// Copyright Epic Games, Inc. All Rights Reserved. #include "CreateBlueprintFromActorDialog.h" #include "AssetRegistry/AssetData.h" #include "AssetRegistry/AssetRegistryModule.h" #include "AssetRegistry/IAssetRegistry.h" #include "AssetToolsModule.h" #include "ClassViewerFilter.h" #include "ClassViewerModule.h" #include "Components/ActorComponent.h" #include "Containers/Array.h" #include "Containers/Set.h" #include "ContentBrowserDataSubsystem.h" #include "ContentBrowserDelegates.h" #include "ContentBrowserItemPath.h" #include "ContentBrowserModule.h" #include "Delegates/Delegate.h" #include "Editor.h" #include "Editor/EditorEngine.h" #include "Engine/Blueprint.h" #include "Fonts/SlateFontInfo.h" #include "Framework/Notifications/NotificationManager.h" #include "GameFramework/Actor.h" #include "HAL/PlatformCrt.h" #include "HAL/PlatformMisc.h" #include "IAssetTools.h" #include "IContentBrowserSingleton.h" #include "Input/Events.h" #include "Input/Reply.h" #include "InputCoreTypes.h" #include "Internationalization/Internationalization.h" #include "Internationalization/Text.h" #include "Kismet2/KismetEditorUtilities.h" #include "Layout/Children.h" #include "Layout/Margin.h" #include "Layout/Visibility.h" #include "Math/Color.h" #include "Math/Vector2D.h" #include "Misc/AssertionMacros.h" #include "Misc/Attribute.h" #include "Misc/FileHelper.h" #include "Misc/MessageDialog.h" #include "Misc/Paths.h" #include "Modules/ModuleManager.h" #include "PackageTools.h" #include "SClassViewer.h" #include "SPrimaryButton.h" #include "SSimpleButton.h" #include "Selection.h" #include "SlotBase.h" #include "Styling/AppStyle.h" #include "Styling/CoreStyle.h" #include "Styling/ISlateStyle.h" #include "Styling/SlateColor.h" #include "Styling/SlateTypes.h" #include "Styling/StyleColors.h" #include "Templates/Casts.h" #include "Templates/SharedPointer.h" #include "Templates/UnrealTemplate.h" #include "Types/SlateEnums.h" #include "UObject/Class.h" #include "UObject/NameTypes.h" #include "UObject/Object.h" #include "UObject/ObjectMacros.h" #include "UObject/WeakObjectPtr.h" #include "UObject/WeakObjectPtrTemplates.h" #include "Widgets/DeclarativeSyntaxSupport.h" #include "Widgets/Images/SImage.h" #include "Widgets/Input/SButton.h" #include "Widgets/Input/SCheckBox.h" #include "Widgets/Input/SEditableTextBox.h" #include "Widgets/Layout/SBorder.h" #include "Widgets/Layout/SBox.h" #include "Widgets/Layout/SExpandableArea.h" #include "Widgets/Layout/SGridPanel.h" #include "Widgets/Layout/SUniformGridPanel.h" #include "Widgets/Notifications/SNotificationList.h" #include "Widgets/SBoxPanel.h" #include "Widgets/SCompoundWidget.h" #include "Widgets/SWindow.h" #include "Widgets/Text/STextBlock.h" struct FGeometry; #define LOCTEXT_NAMESPACE "CreateBlueprintFromActorDialog" class SSCreateBlueprintPicker : public SCompoundWidget { public: SLATE_BEGIN_ARGS(SSCreateBlueprintPicker) {} SLATE_ARGUMENT(TSharedPtr, ParentWindow) SLATE_ARGUMENT(AActor*, ActorOverride) SLATE_ARGUMENT(ECreateBlueprintFromActorMode, CreateMode) SLATE_END_ARGS() /** Constructs this widget with InArgs */ void Construct(const FArguments& InArgs); /** Handler for when a class is picked in the class picker */ void OnClassPicked(UClass* InChosenClass); /** Handler for when "Ok" we selected in the class viewer */ FReply OnClassPickerConfirmed(); /** Handler for when "Cancel" we selected in the class viewer */ FReply OnClassPickerCanceled(); /** Handler for when "..." is clicked to pick an asset path */ FReply OnPathPickerSummoned(); /** Handler for the custom button to hide/unhide the class viewer */ void OnCustomAreaExpansionChanged(bool bExpanded); /** Callback when the user changes the filename for the Blueprint */ void OnFilenameChanged(const FText& InNewName); /** Common function to evaluate whether the asset name given the asset path context, is valid */ void UpdateFilenameStatus(); /** select button visibility delegate */ EVisibility GetSelectButtonVisibility() const; ECheckBoxState IsCreateModeChecked(ECreateBlueprintFromActorMode InCreateMode) const { return (CreateMode == InCreateMode ? ECheckBoxState::Checked : ECheckBoxState::Unchecked); }; void OnCreateModeChanged(ECheckBoxState NewCheckedState, ECreateBlueprintFromActorMode InCreateMode) { if (NewCheckedState == ECheckBoxState::Checked) { CreateMode = InCreateMode; ClassViewer->Refresh(); } }; FText GetCreateMethodTooltip(ECreateBlueprintFromActorMode InCreateMode, bool bEnabled) const; FSlateColor GetCreateModeTextColor(ECreateBlueprintFromActorMode InCreateMode) const { return (CreateMode == InCreateMode ? FSlateColor(FLinearColor(0, 0, 0)) : FSlateColor(FLinearColor(0.72f, 0.72f, 0.72f, 1.f))); }; /** Overridden from SWidget: Called when a key is pressed down - capturing copy */ FReply OnKeyDown(const FGeometry& MyGeometry, const FKeyEvent& InKeyEvent); /** A pointer to the window that is asking the user to select a parent class */ TWeakPtr WeakParentWindow; /** A pointer to a class viewer **/ TSharedPtr ClassViewer; /** Filename textbox widget */ TSharedPtr FileNameWidget; /** The class that was last clicked on */ UClass* ChosenClass; /** The actor that was passed in */ TWeakObjectPtr ActorOverride; /** The path the asset should be created at */ FContentBrowserItemPath AssetPath; /** The the name for the new asset */ FString AssetName; /** The method to use when creating the actor */ ECreateBlueprintFromActorMode CreateMode; /** A flag indicating that Ok was selected */ bool bPressedOk; /** A flag indicating the current asset name is invalid */ bool bIsReportingError; }; ECreateBlueprintFromActorMode FCreateBlueprintFromActorDialog::GetValidCreationMethods() { int32 NumSelectedActors = 0; bool bCanHarvestComponents = false; bool bCanSubclass = true; bool bCanCreatePrefab = true; for (FSelectionIterator Iter(*GEditor->GetSelectedActors()); Iter; ++Iter) { AActor* Actor = Cast(*Iter); if (Actor) { if (NumSelectedActors == 0) { bCanSubclass = FKismetEditorUtilities::CanCreateBlueprintOfClass(Actor->GetClass()); if (bCanSubclass) { // Check whether the class is allowed by the global class filter FClassViewerModule& ClassViewerModule = FModuleManager::LoadModuleChecked("ClassViewer"); if (const TSharedPtr& GlobalClassFilter = ClassViewerModule.GetGlobalClassViewerFilter()) { TSharedRef ClassFilterFuncs = ClassViewerModule.CreateFilterFuncs(); FClassViewerInitializationOptions ClassViewerOptions = {}; bCanSubclass = GlobalClassFilter->IsClassAllowed(ClassViewerOptions, Actor->GetClass(), ClassFilterFuncs); } } } if (bCanCreatePrefab && Actor->GetClass()->HasAnyClassFlags(CLASS_NotPlaceable)) { bCanCreatePrefab = false; } if (!bCanHarvestComponents) { for (UActorComponent* Component : Actor->GetComponents()) { if (Component && FKismetEditorUtilities::IsClassABlueprintSpawnableComponent(Component->GetClass())) { bCanHarvestComponents = true; break; } } } } ++NumSelectedActors; } ECreateBlueprintFromActorMode ValidCreationMethods = ECreateBlueprintFromActorMode::None; if (NumSelectedActors > 0) { if (bCanHarvestComponents) { ValidCreationMethods |= ECreateBlueprintFromActorMode::Harvest; } if (bCanCreatePrefab) { ValidCreationMethods |= ECreateBlueprintFromActorMode::ChildActor; } if (bCanSubclass && NumSelectedActors == 1) { ValidCreationMethods |= ECreateBlueprintFromActorMode::Subclass; } } return ValidCreationMethods; } FText SSCreateBlueprintPicker::GetCreateMethodTooltip(ECreateBlueprintFromActorMode InCreateMode, bool bEnabled) const { if (!bEnabled) { switch (InCreateMode) { case ECreateBlueprintFromActorMode::Subclass: { int32 NumSelectedActors = 0; UClass* SelectedActorClass = nullptr; for (FSelectionIterator Iter(*GEditor->GetSelectedActors()); Iter; ++Iter) { if (AActor* Actor = Cast(*Iter)) { SelectedActorClass = Actor->GetClass(); } ++NumSelectedActors; } if (NumSelectedActors == 1) { return FText::Format(LOCTEXT("SubClassDisabled_InvalidBlueprintType", "Cannot create blueprint subclass of '{0}'."), (SelectedActorClass ? SelectedActorClass->GetDisplayNameText() : FText::GetEmpty())); } else { return LOCTEXT("SubClassDisabled_MultipleSelection", "Cannot subclass when multiple actors are selected."); } } case ECreateBlueprintFromActorMode::ChildActor: return LOCTEXT("ChildActorDisabled", "No selected actor can be spawned as a child actor."); case ECreateBlueprintFromActorMode::Harvest: return LOCTEXT("HavestDisabled", "No harvestable components in selected actors."); } } return FText::GetEmpty(); } void SSCreateBlueprintPicker::Construct(const FArguments& InArgs) { WeakParentWindow = InArgs._ParentWindow; bPressedOk = false; ChosenClass = nullptr; ECreateBlueprintFromActorMode ValidCreateMethods = FCreateBlueprintFromActorDialog::GetValidCreationMethods(); const bool bCanHarvestComponents = !!(ValidCreateMethods & ECreateBlueprintFromActorMode::Harvest); const bool bCanSubclass = !!(ValidCreateMethods & ECreateBlueprintFromActorMode::Subclass); const bool bCanCreatePrefab = !!(ValidCreateMethods & ECreateBlueprintFromActorMode::ChildActor); if (!!(InArgs._CreateMode & ValidCreateMethods)) { CreateMode = InArgs._CreateMode; } else { if (bCanSubclass) { CreateMode = ECreateBlueprintFromActorMode::Subclass; } else if (bCanCreatePrefab) { CreateMode = ECreateBlueprintFromActorMode::ChildActor; } else if (bCanHarvestComponents) { CreateMode = ECreateBlueprintFromActorMode::Harvest; } else { CreateMode = ECreateBlueprintFromActorMode::None; } } // Set initial destination asset folder and name { FContentBrowserModule& ContentBrowserModule = FModuleManager::LoadModuleChecked("ContentBrowser"); AssetPath = ContentBrowserModule.Get().GetCurrentPath(); // Change path if cannot write to it AssetPath = ContentBrowserModule.Get().GetInitialPathToSaveAsset(AssetPath); for (FSelectionIterator Iter(*GEditor->GetSelectedActors()); Iter; ++Iter) { AActor* Actor = Cast(*Iter); if (Actor) { AssetName += Actor->GetActorLabel(); AssetName += TEXT("_"); break; } } AssetName = UPackageTools::SanitizePackageName(AssetName + TEXT("Blueprint")); } ActorOverride = InArgs._ActorOverride; FClassViewerInitializationOptions ClassViewerOptions; ClassViewerOptions.Mode = EClassViewerMode::ClassPicker; ClassViewerOptions.DisplayMode = EClassViewerDisplayMode::TreeView; ClassViewerOptions.bShowObjectRootClass = true; ClassViewerOptions.bIsPlaceableOnly = true; ClassViewerOptions.bIsBlueprintBaseOnly = true; ClassViewerOptions.bShowUnloadedBlueprints = true; ClassViewerOptions.bEnableClassDynamicLoading = true; ClassViewerOptions.NameTypeToDisplay = EClassViewerNameTypeToDisplay::Dynamic; if (ActorOverride == nullptr) { USelection* SelectedActors = GEditor->GetSelectedActors(); if (SelectedActors->Num() == 1) { ActorOverride = CastChecked(SelectedActors->GetSelectedObject(0)); } } class FBlueprintFromActorParentFilter : public IClassViewerFilter { public: FBlueprintFromActorParentFilter(UClass* InAllowedClass, ECreateBlueprintFromActorMode& InCreateModeRef) : CreateModeRef(InCreateModeRef) { AllowedClass.Add(InAllowedClass); } TSet AllowedClass; const ECreateBlueprintFromActorMode& CreateModeRef; virtual bool IsClassAllowed(const FClassViewerInitializationOptions& InInitOptions, const UClass* InClass, TSharedRef< FClassViewerFilterFuncs > InFilterFuncs) override { return CreateModeRef != ECreateBlueprintFromActorMode::Subclass || InFilterFuncs->IfInChildOfClassesSet(AllowedClass, InClass) == EFilterReturn::Passed; } virtual bool IsUnloadedClassAllowed(const FClassViewerInitializationOptions& InInitOptions, const TSharedRef< const IUnloadedBlueprintData > InUnloadedClassData, TSharedRef< FClassViewerFilterFuncs > InFilterFuncs) override { return CreateModeRef != ECreateBlueprintFromActorMode::Subclass || InFilterFuncs->IfInChildOfClassesSet(AllowedClass, InUnloadedClassData) == EFilterReturn::Passed; } }; UClass* ActorOverrideClass = nullptr; if (ActorOverride.IsValid()) { ActorOverrideClass = ActorOverride->GetClass(); TSharedPtr Filter = MakeShareable(new FBlueprintFromActorParentFilter(ActorOverrideClass, CreateMode)); ClassViewerOptions.ClassFilters.Add(Filter.ToSharedRef()); } if (ActorOverrideClass && CreateMode == ECreateBlueprintFromActorMode::Subclass) { ClassViewerOptions.InitiallySelectedClass = ActorOverrideClass; } else { ClassViewerOptions.InitiallySelectedClass = AActor::StaticClass(); } { const FString DestPackageName = FPaths::Combine(AssetPath.GetInternalPathString(), AssetName); const FString DestAssetPath = FString::Printf(TEXT("%s.%s"), *DestPackageName, *AssetName); ClassViewerOptions.AdditionalReferencingAssets.Add(FAssetData(DestPackageName, DestAssetPath, UBlueprint::StaticClass()->GetClassPathName())); // @fixme: Update the class viewer whenever the destination folder changes } FClassViewerModule& ClassViewerModule = FModuleManager::LoadModuleChecked("ClassViewer"); ClassViewer = StaticCastSharedRef(ClassViewerModule.CreateClassViewer(ClassViewerOptions, FOnClassPicked::CreateSP(this, &SSCreateBlueprintPicker::OnClassPicked))); FString PackageName; FAssetToolsModule& AssetToolsModule = FModuleManager::LoadModuleChecked("AssetTools"); AssetToolsModule.Get().CreateUniqueAssetName(AssetPath.GetInternalPathString() / AssetName, TEXT(""), PackageName, AssetName); TSharedPtr CreationMethodSection; struct FCreateModeDetails { FText Label; FText Description; ECreateBlueprintFromActorMode CreateMode; bool bEnabled; }; const FCreateModeDetails CreateModeDetails[3] = { { LOCTEXT("CreateMode_Subclass", "New Subclass"), LOCTEXT("CreateMode_Subclass_Description", "Replace the selected actor with an instance of a new Blueprint Class inherited from the selected parent class."), ECreateBlueprintFromActorMode::Subclass, bCanSubclass }, { LOCTEXT("CreateMode_ChildActor", "Child Actors"), LOCTEXT("CreateMode_ChildActor_Description", "Replace the selected actors with an instance of a new Blueprint Class inherited from the selected parent class with each of the selected Actors as a Child Actor."), ECreateBlueprintFromActorMode::ChildActor, bCanCreatePrefab }, { LOCTEXT("CreateMode_Harvest", "Harvest Components"), LOCTEXT("CreateMode_Harvest_Description", "Replace the selected actors with an instance of a new Blueprint Class inherited from the selected parent class that contains the components."), ECreateBlueprintFromActorMode::Harvest, bCanHarvestComponents } }; const FCheckBoxStyle& RadioStyle = FAppStyle::Get().GetWidgetStyle("SegmentedCombo.ButtonOnly"); SAssignNew(CreationMethodSection, SGridPanel) .FillColumn(1, 1.f); for (int32 Index = 0; Index < UE_ARRAY_COUNT(CreateModeDetails); ++Index) { float TopPadding = Index == 0 ? 28.0f : 16.0f; float BottomPadding = Index == UE_ARRAY_COUNT(CreateModeDetails) - 1 ? 28.0f : 16.0f; CreationMethodSection->AddSlot(0, Index) .Padding(10.0f, TopPadding, 5.0f, BottomPadding) .VAlign(VAlign_Center) [ SNew(SCheckBox) .Style(&RadioStyle) .IsEnabled(CreateModeDetails[Index].bEnabled) .IsChecked_Raw(this, &SSCreateBlueprintPicker::IsCreateModeChecked, CreateModeDetails[Index].CreateMode) .OnCheckStateChanged(this, &SSCreateBlueprintPicker::OnCreateModeChanged, CreateModeDetails[Index].CreateMode) .ToolTipText_Raw(this, &SSCreateBlueprintPicker::GetCreateMethodTooltip, CreateModeDetails[Index].CreateMode, CreateModeDetails[Index].bEnabled) [ SNew(SHorizontalBox) + SHorizontalBox::Slot() .VAlign(VAlign_Center) .HAlign(HAlign_Left) .Padding(-8, 3, 0, 3) .AutoWidth() [ SNew(SImage) .Image(FAppStyle::Get().GetBrush("Icons.Blueprints")) ] + SHorizontalBox::Slot() .VAlign(VAlign_Center) .HAlign(HAlign_Left) .Padding(4, 3, 0, 3) .AutoWidth() [ SNew(STextBlock) .Text(CreateModeDetails[Index].Label) .TextStyle(FAppStyle::Get(), "NormalText") .ColorAndOpacity(FStyleColors::White) ] ] ]; CreationMethodSection->AddSlot(1, Index) .Padding(4.0f, TopPadding, 1.0f, BottomPadding) .VAlign(VAlign_Center) [ SNew(STextBlock) .Text(CreateModeDetails[Index].Description) .TextStyle(FAppStyle::Get(), "SmallText") .IsEnabled(CreateModeDetails[Index].bEnabled) .AutoWrapText(true) ]; } ChildSlot [ SNew(SBorder) .Visibility(EVisibility::Visible) .BorderImage(FAppStyle::Get().GetBrush("Brushes.Panel")) [ SNew(SBox) .Visibility(EVisibility::Visible) [ SNew(SVerticalBox) +SVerticalBox::Slot() .AutoHeight() .Padding(0.0f, 0.0f, 0.0f, 0.0f) [ SNew(SExpandableArea) .AreaTitle(LOCTEXT("CreationMethod", "Creation Method")) .AreaTitleFont(FCoreStyle::Get().GetFontStyle("NormalFontBold")) .BorderImage(FAppStyle::Get().GetBrush("ToolPanel.GroupBorder")) .BodyBorderImage(FAppStyle::Get().GetBrush("Brushes.Recessed")) .BodyContent() [ CreationMethodSection.ToSharedRef() ] ] +SVerticalBox::Slot() .FillHeight(1.f) .Padding(0.0f, 1.0f, 0.0f, 0.0f) [ SNew(SExpandableArea) .MaxHeight(320.f) .InitiallyCollapsed(false) .AreaTitle(NSLOCTEXT("SClassPickerDialog", "ParentClassAreaTitle", "Parent Class")) .AreaTitleFont(FCoreStyle::Get().GetFontStyle("NormalFontBold")) .BorderImage(FAppStyle::Get().GetBrush("ToolPanel.GroupBorder")) .BodyBorderImage(FAppStyle::Get().GetBrush("Brushes.Recessed")) .OnAreaExpansionChanged(this, &SSCreateBlueprintPicker::OnCustomAreaExpansionChanged) .BodyContent() [ ClassViewer.ToSharedRef() ] ] +SVerticalBox::Slot() .AutoHeight() .Padding(0.0f, 10.0f, 0.0f, 0.0f) [ SNew(SGridPanel) .FillColumn(1, 1.f) +SGridPanel::Slot(0, 0) .Padding(16.0f, 0.0f, 13.0f, 7.0f) .VAlign(VAlign_Center) [ SNew(STextBlock) .Text(LOCTEXT("CreateBlueprintFromActor_NameLabel", "Blueprint Name")) ] +SGridPanel::Slot(1, 0) .Padding(0.0f, 0.0f, 55.0f, 10.0f) [ SAssignNew(FileNameWidget, SEditableTextBox) .Text(FText::FromString(AssetName)) .OnTextChanged(this, &SSCreateBlueprintPicker::OnFilenameChanged) ] +SGridPanel::Slot(0, 1) .Padding(15.0f, 0.0f, 13.0f, 0.0f) .VAlign(VAlign_Center) [ SNew(STextBlock) .Text(LOCTEXT("CreateBlueprintFromActor_PathLabel", "Path")) ] +SGridPanel::Slot(1, 1) [ SNew(SHorizontalBox) +SHorizontalBox::Slot() .FillWidth(1.f) [ SNew(SEditableTextBox) .Text(TAttribute::Create(TAttribute::FGetter::CreateLambda([this]() { return FText::FromName(AssetPath.GetVirtualPathName()); }))) .IsReadOnly(true) ] +SHorizontalBox::Slot() .Padding(8.0f, 0.0f, 19.0f, 0.0f) .AutoWidth() [ SNew(SSimpleButton) .OnClicked(this, &SSCreateBlueprintPicker::OnPathPickerSummoned) .Icon(FAppStyle::Get().GetBrush("Icons.FolderClosed")) ] ] ] +SVerticalBox::Slot() .AutoHeight() .HAlign(HAlign_Right) .VAlign(VAlign_Bottom) .Padding(8.0f) [ SNew(SUniformGridPanel) .SlotPadding(FAppStyle::GetMargin("StandardDialog.SlotPadding")) +SUniformGridPanel::Slot(0,0) [ SNew(SPrimaryButton) .Text(NSLOCTEXT("SClassPickerDialog", "ClassPickerSelectButton", "Select")) .Visibility( this, &SSCreateBlueprintPicker::GetSelectButtonVisibility ) .OnClicked(this, &SSCreateBlueprintPicker::OnClassPickerConfirmed) ] +SUniformGridPanel::Slot(1,0) [ SNew(SButton) .Text(NSLOCTEXT("SClassPickerDialog", "ClassPickerCancelButton", "Cancel")) .OnClicked(this, &SSCreateBlueprintPicker::OnClassPickerCanceled) ] ] ] ] ]; if (WeakParentWindow.IsValid()) { WeakParentWindow.Pin().Get()->SetWidgetToFocusOnActivate(ClassViewer); } } void SSCreateBlueprintPicker::OnClassPicked(UClass* InChosenClass) { ChosenClass = InChosenClass; } FReply SSCreateBlueprintPicker::OnClassPickerConfirmed() { if (ChosenClass == nullptr) { FMessageDialog::Open(EAppMsgType::Ok, NSLOCTEXT("EditorFactories", "MustChooseClassWarning", "You must choose a class.")); } else if (bIsReportingError) { FMessageDialog::Open(EAppMsgType::Ok, LOCTEXT("InvalidAssetname", "You must specify a valid asset name.")); } else { bPressedOk = true; if (WeakParentWindow.IsValid()) { WeakParentWindow.Pin()->RequestDestroyWindow(); } } return FReply::Handled(); } FReply SSCreateBlueprintPicker::OnClassPickerCanceled() { if (WeakParentWindow.IsValid()) { WeakParentWindow.Pin()->RequestDestroyWindow(); } return FReply::Handled(); } class SSCreateBlueprintPathPicker : public SCompoundWidget { SLATE_BEGIN_ARGS(SSCreateBlueprintPathPicker) {} SLATE_ARGUMENT(TSharedPtr, ParentWindow) SLATE_ARGUMENT(FContentBrowserItemPath, AssetPath) SLATE_END_ARGS() void Construct(const FArguments& InArgs); /** Callback when the selected asset path has changed. */ void OnSelectAssetPath(const FString& InVirtualPath) { AssetPath.SetPathFromString(InVirtualPath, EContentBrowserPathType::Virtual); } /** Callback when the "ok" button is clicked. */ FReply OnClickOk(); /** Destroys the window when the operation is cancelled. */ FReply OnClickCancel(); /** A pointer to the window that is asking the user to select a parent class */ TWeakPtr WeakParentWindow; FContentBrowserItemPath AssetPath; bool bPressedOk; }; void SSCreateBlueprintPathPicker::Construct(const FArguments& InArgs) { WeakParentWindow = InArgs._ParentWindow; bPressedOk = false; AssetPath = InArgs._AssetPath; FContentBrowserModule& ContentBrowserModule = FModuleManager::LoadModuleChecked("ContentBrowser"); FPathPickerConfig PathPickerConfig; PathPickerConfig.DefaultPath = AssetPath.GetVirtualPathString(); PathPickerConfig.OnPathSelected = FOnPathSelected::CreateRaw(this, &SSCreateBlueprintPathPicker::OnSelectAssetPath); PathPickerConfig.bAllowReadOnlyFolders = false; PathPickerConfig.bOnPathSelectedPassesVirtualPaths = true; ChildSlot [ SNew(SVerticalBox) + SVerticalBox::Slot() .FillHeight(1.0f) [ ContentBrowserModule.Get().CreatePathPicker(PathPickerConfig) ] + SVerticalBox::Slot() .HAlign(HAlign_Right) .Padding(0, 20, 0, 0) .AutoHeight() [ SNew(SHorizontalBox) + SHorizontalBox::Slot() .Padding(0, 2, 6, 0) .AutoWidth() [ SNew(SButton) .VAlign(VAlign_Bottom) .ContentPadding(FMargin(8, 2, 8, 2)) .OnClicked(this, &SSCreateBlueprintPathPicker::OnClickOk) .ButtonStyle(FAppStyle::Get(), "FlatButton.Success") .TextStyle(FAppStyle::Get(), "FlatButton.DefaultTextStyle") .Text(LOCTEXT("OkButtonText", "OK")) ] + SHorizontalBox::Slot() .Padding(0, 2, 0, 0) .AutoWidth() [ SNew(SButton) .VAlign(VAlign_Bottom) .ContentPadding(FMargin(8, 2, 8, 2)) .OnClicked(this, &SSCreateBlueprintPathPicker::OnClickCancel) .ButtonStyle(FAppStyle::Get(), "FlatButton.Default") .TextStyle(FAppStyle::Get(), "FlatButton.DefaultTextStyle") .Text(LOCTEXT("CancelButtonText", "Cancel")) ] ] ]; }; FReply SSCreateBlueprintPathPicker::OnClickOk() { bPressedOk = true; if (WeakParentWindow.IsValid()) { WeakParentWindow.Pin()->RequestDestroyWindow(); } return FReply::Handled(); } FReply SSCreateBlueprintPathPicker::OnClickCancel() { if (WeakParentWindow.IsValid()) { WeakParentWindow.Pin()->RequestDestroyWindow(); } return FReply::Handled(); } FReply SSCreateBlueprintPicker::OnPathPickerSummoned() { // Create the window to pick the class TSharedRef PickerWindow = SNew(SWindow) .Title(LOCTEXT("CreateBlueprintFromActors_PickPath", "Select Path")) .SizingRule(ESizingRule::UserSized) .ClientSize(FVector2D(300.f, 400.f)) .SupportsMaximize(false) .SupportsMinimize(false); TSharedRef PathPickerDialog = SNew(SSCreateBlueprintPathPicker) .ParentWindow(PickerWindow) .AssetPath(AssetPath); PickerWindow->SetContent(PathPickerDialog); GEditor->EditorAddModalWindow(PickerWindow); if (PathPickerDialog->bPressedOk) { AssetPath.SetPathFromString(PathPickerDialog->AssetPath.GetVirtualPathString(), EContentBrowserPathType::Virtual); UpdateFilenameStatus(); } return FReply::Handled(); } void SSCreateBlueprintPicker::OnCustomAreaExpansionChanged(bool bExpanded) { if (bExpanded && WeakParentWindow.IsValid()) { WeakParentWindow.Pin().Get()->SetWidgetToFocusOnActivate(ClassViewer); } } void SSCreateBlueprintPicker::OnFilenameChanged(const FText& InNewName) { AssetName = InNewName.ToString(); UpdateFilenameStatus(); } void SSCreateBlueprintPicker::UpdateFilenameStatus() { FText ErrorText; if (!FFileHelper::IsFilenameValidForSaving(AssetName, ErrorText) || !FName(*AssetName).IsValidObjectName(ErrorText)) { FileNameWidget->SetError(ErrorText); bIsReportingError = true; return; } else { TArray AssetData; FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked("AssetRegistry"); AssetRegistryModule.Get().GetAssetsByPath(AssetPath.GetInternalPathName(), AssetData); // Check to see if the name conflicts for (const FAssetData& Data : AssetData) { const FString& AssetNameStr = Data.AssetName.ToString(); if (Data.AssetName.ToString() == AssetName) { FileNameWidget->SetError(LOCTEXT("AssetInUseError", "Asset name already in use!")); bIsReportingError = true; return; } } } FileNameWidget->SetError(FText::GetEmpty()); bIsReportingError = false; } EVisibility SSCreateBlueprintPicker::GetSelectButtonVisibility() const { EVisibility ButtonVisibility = EVisibility::Visible; if (ChosenClass == nullptr || bIsReportingError) { ButtonVisibility = EVisibility::Hidden; } else if (CreateMode == ECreateBlueprintFromActorMode::Subclass) { UObject* SelectedActor = GEditor->GetSelectedActors()->GetSelectedObject(0); if (SelectedActor == nullptr || !ChosenClass->IsChildOf(SelectedActor->GetClass())) { ButtonVisibility = EVisibility::Hidden; } } return ButtonVisibility; } /** Overridden from SWidget: Called when a key is pressed down - capturing copy */ FReply SSCreateBlueprintPicker::OnKeyDown(const FGeometry& MyGeometry, const FKeyEvent& InKeyEvent) { WeakParentWindow.Pin().Get()->SetWidgetToFocusOnActivate(ClassViewer); if (InKeyEvent.GetKey() == EKeys::Escape) { return OnClassPickerCanceled(); } else if (InKeyEvent.GetKey() == EKeys::Enter) { OnClassPickerConfirmed(); } else { return ClassViewer->OnKeyDown(MyGeometry, InKeyEvent); } return FReply::Handled(); } void FCreateBlueprintFromActorDialog::OpenDialog(ECreateBlueprintFromActorMode CreateMode, AActor* InActorOverride, bool bInReplaceActors) { TWeakObjectPtr ActorOverride(InActorOverride); // Create the window to pick the class TSharedRef PickerWindow = SNew(SWindow) .Title(LOCTEXT("CreateBlueprintFromActors","Create Blueprint From Selection")) .SizingRule(ESizingRule::UserSized) .ClientSize(FVector2D(600.f, 600.f)) .SupportsMaximize(false) .SupportsMinimize(false); TSharedRef ClassPickerDialog = SNew(SSCreateBlueprintPicker) .ParentWindow(PickerWindow) .ActorOverride(InActorOverride) .CreateMode(CreateMode); PickerWindow->SetContent(ClassPickerDialog); GEditor->EditorAddModalWindow(PickerWindow); if (ClassPickerDialog->bPressedOk) { if (ClassPickerDialog->AssetPath.HasInternalPath()) { FString NewAssetName = ClassPickerDialog->AssetPath.GetInternalPathString() / ClassPickerDialog->AssetName; OnCreateBlueprint(NewAssetName, ClassPickerDialog->ChosenClass, ClassPickerDialog->CreateMode, ActorOverride.Get(), bInReplaceActors); } else { FNotificationInfo ErrorNotificationInfo(FText::FormatOrdered(LOCTEXT("PathError", "Could not convert virtual path '{0}' to internal path."), FText::FromString(*ClassPickerDialog->AssetPath.GetVirtualPathString()))); TSharedPtr NotificationItem = FSlateNotificationManager::Get().AddNotification(ErrorNotificationInfo); NotificationItem->SetCompletionState(SNotificationItem::CS_Fail); } } } void FCreateBlueprintFromActorDialog::OnCreateBlueprint(const FString& InAssetPath, UClass* ParentClass, ECreateBlueprintFromActorMode CreateMode, AActor* ActorToUse, bool bInReplaceActors) { UBlueprint* Blueprint = nullptr; switch (CreateMode) { case ECreateBlueprintFromActorMode::Harvest: { TArray Actors; USelection* SelectedActors = GEditor->GetSelectedActors(); for(FSelectionIterator Iter(*SelectedActors); Iter; ++Iter) { // We only care about actors that are referenced in the world for literals, and also in the same level as this blueprint if (AActor* Actor = Cast(*Iter)) { Actors.Add(Actor); } } FKismetEditorUtilities::FHarvestBlueprintFromActorsParams Params; Params.bReplaceActors = true; Params.ParentClass = ParentClass; Blueprint = FKismetEditorUtilities::HarvestBlueprintFromActors(InAssetPath, Actors, Params); } break; case ECreateBlueprintFromActorMode::Subclass: { if (!ActorToUse) { TArray SelectedActors; GEditor->GetSelectedActors()->GetSelectedObjects(AActor::StaticClass(), SelectedActors); check(SelectedActors.Num() == 1); ActorToUse = Cast(SelectedActors[0]); } FKismetEditorUtilities::FCreateBlueprintFromActorParams Params; Params.bReplaceActor = true; Params.ParentClassOverride = ParentClass; Blueprint = FKismetEditorUtilities::CreateBlueprintFromActor(InAssetPath, ActorToUse, Params); } break; case ECreateBlueprintFromActorMode::ChildActor: { TArray Actors; USelection* SelectedActors = GEditor->GetSelectedActors(); for (FSelectionIterator Iter(*SelectedActors); Iter; ++Iter) { // We only care about actors that are referenced in the world for literals, and also in the same level as this blueprint if (AActor* Actor = Cast(*Iter)) { Actors.Add(Actor); } } FKismetEditorUtilities::FCreateBlueprintFromActorsParams Params(Actors); Params.bReplaceActors = true; Params.ParentClass = ParentClass; Blueprint = FKismetEditorUtilities::CreateBlueprintFromActors(InAssetPath, Params); } break; } if (Blueprint) { // Select the newly created blueprint in the content browser, but don't activate the browser TArray Objects; Objects.Add(Blueprint); GEditor->SyncBrowserToObjects( Objects, false ); } else { FNotificationInfo Info( LOCTEXT("CreateBlueprintFromActorFailed", "Unable to create a blueprint from actor.") ); Info.ExpireDuration = 3.0f; Info.bUseLargeFont = false; TSharedPtr Notification = FSlateNotificationManager::Get().AddNotification(Info); if ( Notification.IsValid() ) { Notification->SetCompletionState( SNotificationItem::CS_Fail ); } } } #undef LOCTEXT_NAMESPACE