// Copyright Epic Games, Inc. All Rights Reserved. #include "AssetRegistry/AssetData.h" #include "AssetRegistry/AssetRegistryModule.h" #include "AssetRegistry/IAssetRegistry.h" #include "AssetToolsModule.h" #include "AssetTypeCategories.h" #include "BlutilityContentBrowserExtensions.h" #include "BlutilityLevelEditorExtensions.h" #include "BlutilityUMGEditorExtensions.h" #include "Containers/Array.h" #include "Containers/UnrealString.h" #include "Delegates/Delegate.h" #include "Editor.h" #include "Editor/EditorEngine.h" #include "EditorFunctionLibrary.h" #include "EditorSupportDelegates.h" #include "EditorUtilityActor.h" #include "EditorUtilityBlueprint.h" #include "EditorUtilityCamera.h" #include "EditorUtilityCommon.h" #include "EditorUtilityObject.h" #include "EditorUtilitySubsystem.h" #include "EditorUtilityWidget.h" #include "EditorUtilityWidgetBlueprint.h" #include "Engine/Blueprint.h" #include "Engine/Engine.h" #include "Engine/World.h" #include "Framework/Docking/TabManager.h" #include "Framework/Docking/WorkspaceItem.h" #include "GlobalEditorUtilityBase.h" #include "HAL/Platform.h" #include "HAL/PlatformCrt.h" #include "IAssetTools.h" #include "IBlutilityModule.h" #include "Internationalization/Internationalization.h" #include "Internationalization/Text.h" #include "Kismet2/BlueprintEditorUtils.h" #include "KismetCompiler.h" #include "KismetCompilerModule.h" #include "LevelEditor.h" #include "Logging/LogCategory.h" #include "Logging/LogMacros.h" #include "Misc/AssertionMacros.h" #include "Modules/ModuleManager.h" #include "Styling/AppStyle.h" #include "Templates/Casts.h" #include "Templates/SharedPointer.h" #include "Templates/SubclassOf.h" #include "Textures/SlateIcon.h" #include "Trace/Detail/Channel.h" #include "UMGEditorModule.h" #include "UObject/Class.h" #include "UObject/GCObject.h" #include "UObject/NameTypes.h" #include "UObject/Object.h" #include "UObject/Package.h" #include "UObject/PurgingReferenceCollector.h" #include "UObject/SoftObjectPath.h" #include "UObject/UObjectBase.h" #include "UObject/UObjectGlobals.h" #include "UObject/UObjectHash.h" #include "UnrealEdMisc.h" #include "WidgetBlueprint.h" #include "WidgetBlueprintCompiler.h" #include "Widgets/Docking/SDockTab.h" #include "WorkspaceMenuStructure.h" #include "WorkspaceMenuStructureModule.h" #include "EditorUtilityWidgetSettingsCustomization.h" #include "EditorUtilityWidgetProjectSettings.h" #include "PropertyEditorModule.h" #define LOCTEXT_NAMESPACE "AssetTypeActions" DEFINE_LOG_CATEGORY(LogEditorUtilityBlueprint); ///////////////////////////////////////////////////// ///////////////////////////////////////////////////// // FBlutilityModule // Blutility module implementation (private) class FBlutilityModule : public IBlutilityModule, public FGCObject { public: virtual void StartupModule() override { // Register the asset type IAssetTools& AssetTools = FModuleManager::LoadModuleChecked("AssetTools").Get(); EditorUtilityAssetCategory = AssetTools.RegisterAdvancedAssetCategory(FName(TEXT("Editor Utilities")), LOCTEXT("EditorUtilitiesAssetCategory", "Editor Utilities")); FKismetCompilerContext::RegisterCompilerForBP(UEditorUtilityWidgetBlueprint::StaticClass(), &UWidgetBlueprint::GetCompilerForWidgetBP); // Register widget blueprint compiler we do this no matter what - @todo: can i remove this? we're double registering.. IUMGEditorModule& UMGEditorModule = FModuleManager::LoadModuleChecked("UMGEditor"); IKismetCompilerInterface& KismetCompilerModule = FModuleManager::LoadModuleChecked("KismetCompiler"); KismetCompilerModule.GetCompilers().Add(UMGEditorModule.GetRegisteredCompiler()); KismetCompilerModule.OverrideBPTypeForClass(AEditorUtilityActor::StaticClass(), UEditorUtilityBlueprint::StaticClass()); KismetCompilerModule.OverrideBPTypeForClass(AEditorUtilityCamera::StaticClass(), UEditorUtilityBlueprint::StaticClass()); KismetCompilerModule.OverrideBPTypeForClass(UEditorUtilityObject::StaticClass(), UEditorUtilityBlueprint::StaticClass()); KismetCompilerModule.OverrideBPTypeForClass(UEditorFunctionLibrary::StaticClass(), UEditorUtilityBlueprint::StaticClass()); KismetCompilerModule.OverrideBPTypeForClass(UEditorUtilityWidget::StaticClass(), UEditorUtilityWidgetBlueprint::StaticClass()); FBlutilityContentBrowserExtensions::InstallHooks(); FBlutilityLevelEditorExtensions::InstallHooks(); FBlutilityUMGEditorExtensions::InstallHooks(); ScriptedEditorWidgetsGroup = WorkspaceMenu::GetMenuStructure().GetToolsCategory()->AddGroup( LOCTEXT("WorkspaceMenu_EditorUtilityWidgetsGroup", "Editor Utility Widgets"), LOCTEXT("ScriptedEditorWidgetsGroupTooltipText", "Custom editor UI created with Blueprints or Python."), FSlateIcon(FAppStyle::GetAppStyleSetName(), "WorkspaceMenu.AdditionalUI"), true); FLevelEditorModule& LevelEditorModule = FModuleManager::LoadModuleChecked(TEXT("LevelEditor")); LevelEditorModule.OnTabManagerChanged().AddRaw(this, &FBlutilityModule::ReinitializeUIs); LevelEditorModule.OnMapChanged().AddRaw(this, &FBlutilityModule::OnMapChanged); FEditorSupportDelegates::PrepareToCleanseEditorObject.AddRaw(this, &FBlutilityModule::OnPrepareToCleanseEditorObject); FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked(TEXT("AssetRegistry")); AssetRegistryModule.Get().OnAssetRemoved().AddRaw(this, &FBlutilityModule::HandleAssetRemoved); FPropertyEditorModule& PropertyModule = FModuleManager::LoadModuleChecked("PropertyEditor"); PropertyModule.RegisterCustomClassLayout(UEditorUtilityWidgetProjectSettings::StaticClass()->GetFName(), FOnGetDetailCustomizationInstance::CreateStatic(&FEditorUtilityWidgetSettingsCustomization::MakeInstance)); } void ReinitializeUIs() { UEditorUtilitySubsystem* EditorUtilitySubsystem = GEditor->GetEditorSubsystem(); FLevelEditorModule& LevelEditorModule = FModuleManager::GetModuleChecked(TEXT("LevelEditor")); TSharedPtr LevelEditorTabManager = LevelEditorModule.GetLevelEditorTabManager(); TArray CorrectPaths; for (const FSoftObjectPath& BlueprintPath : EditorUtilitySubsystem->LoadedUIs) { UObject* BlueprintObject = BlueprintPath.TryLoad(); if (BlueprintObject && IsValidChecked(BlueprintObject) && !BlueprintObject->IsUnreachable()) { UEditorUtilityWidgetBlueprint* Blueprint = Cast(BlueprintObject); if (Blueprint) { if (Blueprint->GeneratedClass) { const UEditorUtilityWidget* CDO = Blueprint->GeneratedClass->GetDefaultObject(); FName RegistrationName = FName(*(Blueprint->GetPathName() + LOCTEXT("ActiveTabSuffix", "_ActiveTab").ToString())); Blueprint->SetRegistrationName(RegistrationName); FText DisplayName = Blueprint->GetTabDisplayName(); if (LevelEditorTabManager && !LevelEditorTabManager->HasTabSpawner(RegistrationName)) { LevelEditorTabManager->RegisterTabSpawner(RegistrationName, FOnSpawnTab::CreateUObject(Blueprint, &UEditorUtilityWidgetBlueprint::SpawnEditorUITab)) .SetDisplayName(DisplayName) .SetGroup(GetMenuGroup().ToSharedRef()); CorrectPaths.Add(BlueprintPath); } } else { UE_LOG(LogEditorUtilityBlueprint, Warning, TEXT("No generated class for: %s"), *BlueprintPath.ToString()); } } else { UE_LOG(LogEditorUtilityBlueprint, Warning, TEXT("Expected object of class EditorUtilityWidgetBlueprint: %s"), *BlueprintPath.ToString()); } } else { UE_LOG(LogEditorUtilityBlueprint, Warning, TEXT("Could not load: %s"), *BlueprintPath.ToString()); } } EditorUtilitySubsystem->LoadedUIs = CorrectPaths; EditorUtilitySubsystem->SaveConfig(); } void OnMapChanged(UWorld* InWorld, EMapChangeType MapChangeType) { for (const FSoftObjectPath& LoadedUI : GEditor->GetEditorSubsystem()->LoadedUIs) { UEditorUtilityWidgetBlueprint* LoadedEditorUtilityBlueprint = Cast(LoadedUI.ResolveObject()); if (LoadedEditorUtilityBlueprint) { UEditorUtilityWidget* CreatedWidget = LoadedEditorUtilityBlueprint->GetCreatedWidget(); if (CreatedWidget) { if (MapChangeType == EMapChangeType::TearDownWorld) { CreatedWidget->Rename(*CreatedWidget->GetName(), GetTransientPackage()); } else if (MapChangeType == EMapChangeType::LoadMap || MapChangeType == EMapChangeType::NewMap) { UWorld* World = GEditor->GetEditorWorldContext().World(); check(World); CreatedWidget->Rename(*CreatedWidget->GetName(), World); } } } } } virtual void ShutdownModule() override { if (!UObjectInitialized()) { return; } // Unregister widget blueprint compiler we do this no matter what. IUMGEditorModule& UMGEditorModule = FModuleManager::LoadModuleChecked("UMGEditor"); IKismetCompilerInterface& KismetCompilerModule = FModuleManager::LoadModuleChecked("KismetCompiler"); KismetCompilerModule.GetCompilers().Remove(UMGEditorModule.GetRegisteredCompiler()); FBlutilityLevelEditorExtensions::RemoveHooks(); FBlutilityContentBrowserExtensions::RemoveHooks(); FBlutilityUMGEditorExtensions::RemoveHooks(); FEditorSupportDelegates::PrepareToCleanseEditorObject.RemoveAll(this); } virtual bool IsEditorUtilityBlueprint( const UBlueprint* Blueprint ) const override { const UClass* BPClass = Blueprint ? Blueprint->GetClass() : nullptr; const bool bIsExplicitEditorUtility = BPClass && ( BPClass->IsChildOf( UEditorUtilityBlueprint::StaticClass() ) || BPClass->IsChildOf( UEditorUtilityWidgetBlueprint::StaticClass() ) || IsEditorOnlyObject(BPClass) ); if( bIsExplicitEditorUtility ) { return true; } else { // People are frequently defining native editor only UObjects and // using the editor only APIs. For consistency, indicate to callers // that these editor only classes should behave as editor utilities, // unless they are actors that want to run during pie: const UClass* NativeParent = FBlueprintEditorUtils::GetNativeParent(Blueprint); if(NativeParent && IsEditorOnlyObject(NativeParent)) { if (NativeParent->IsChildOf(AActor::StaticClass())) { if(const AActor* NativeCDO = Cast(NativeParent->GetDefaultObject(false))) { return !NativeCDO->IsEditorOnlyLoadedInPIE(); } } return true; } } return false; } virtual TSharedPtr GetMenuGroup() const override { return ScriptedEditorWidgetsGroup; } virtual EAssetTypeCategories::Type GetAssetCategory() const override { return EditorUtilityAssetCategory; } virtual TConstArrayView GetAssetCategories() const override { static const TArray> Categories = { FAssetCategoryPath(LOCTEXT("EditorUtilities", "Editor Utilities")) }; return Categories; } virtual void AddLoadedScriptUI(class UEditorUtilityWidgetBlueprint* InBlueprint) override { UEditorUtilitySubsystem* EditorUtilitySubsystem = GEditor->GetEditorSubsystem(); EditorUtilitySubsystem->LoadedUIs.AddUnique(InBlueprint); EditorUtilitySubsystem->SaveConfig(); } virtual void RemoveLoadedScriptUI(class UEditorUtilityWidgetBlueprint* InBlueprint) override { UEditorUtilitySubsystem* EditorUtilitySubsystem = GEditor->GetEditorSubsystem(); EditorUtilitySubsystem->LoadedUIs.Remove(InBlueprint); EditorUtilitySubsystem->SaveConfig(); } protected: virtual void AddReferencedObjects(FReferenceCollector& Collector) override { } virtual FString GetReferencerName() const override { return TEXT("FBlutilityModule"); } void OnPrepareToCleanseEditorObject(UObject* InObject) { // Gather the list of live Editor Utility instances to purge references from TArray EditorUtilityInstances; ForEachObjectOfClass(UEditorUtilityWidget::StaticClass(), [&EditorUtilityInstances](UObject* InEditorUtilityInstance) { EditorUtilityInstances.Add(InEditorUtilityInstance); }); ForEachObjectOfClass(UDEPRECATED_GlobalEditorUtilityBase::StaticClass(), [&EditorUtilityInstances](UObject* InEditorUtilityInstance) { EditorUtilityInstances.Add(InEditorUtilityInstance); }); if (EditorUtilityInstances.Num() > 0) { // Build up the complete list of objects to purge FPurgingReferenceCollector PurgingReferenceCollector; PurgingReferenceCollector.AddObjectToPurge(InObject); ForEachObjectWithOuter(InObject, [&PurgingReferenceCollector](UObject* InInnerObject) { PurgingReferenceCollector.AddObjectToPurge(InInnerObject); }, true); // Run the purge for each Editor Utility instance FReferenceCollectorArchive& PurgingReferenceCollectorArchive = PurgingReferenceCollector.GetVerySlowReferenceCollectorArchive(); for (UObject* EditorUtilityInstance : EditorUtilityInstances) { PurgingReferenceCollectorArchive.SetSerializingObject(EditorUtilityInstance); EditorUtilityInstance->Serialize(PurgingReferenceCollectorArchive); EditorUtilityInstance->CallAddReferencedObjects(PurgingReferenceCollector); PurgingReferenceCollectorArchive.SetSerializingObject(nullptr); } } } void HandleAssetRemoved(const FAssetData& InAssetData) { bool bDeletingLoadedUI = false; if (!GEditor || !GEditor->GetEditorSubsystem()) { return; } for (const FSoftObjectPath& LoadedUIPath : GEditor->GetEditorSubsystem()->LoadedUIs) { if (LoadedUIPath == InAssetData.GetSoftObjectPath()) { bDeletingLoadedUI = true; break; } } if (bDeletingLoadedUI) { FName UIToCleanup = FName(*(InAssetData.GetObjectPathString() + LOCTEXT("ActiveTabSuffix", "_ActiveTab").ToString())); FLevelEditorModule& LevelEditorModule = FModuleManager::GetModuleChecked(TEXT("LevelEditor")); TSharedPtr LevelEditorTabManager = LevelEditorModule.GetLevelEditorTabManager(); TSharedPtr CurrentTab = LevelEditorTabManager->FindExistingLiveTab(UIToCleanup); if (CurrentTab.IsValid()) { CurrentTab->RequestCloseTab(); } } } protected: /** Scripted Editor Widgets workspace menu item */ TSharedPtr ScriptedEditorWidgetsGroup; EAssetTypeCategories::Type EditorUtilityAssetCategory; }; IMPLEMENT_MODULE( FBlutilityModule, Blutility ); #undef LOCTEXT_NAMESPACE