// Copyright Epic Games, Inc. All Rights Reserved. #include "SceneOutlinerHelpers.h" #include "ActorTreeItem.h" #include "ActorDescTreeItem.h" #include "ActorFolderTreeItem.h" #include "ComponentTreeItem.h" #include "EditorActorFolders.h" #include "EditorClassUtils.h" #include "Engine/Blueprint.h" #include "Misc/PackageName.h" #include "Modules/ModuleManager.h" #include "WorldPartition/WorldPartitionActorDescInstance.h" #include "UObject/Package.h" #define LOCTEXT_NAMESPACE "SceneOutlinerHelpers" namespace SceneOutliner { FString FSceneOutlinerHelpers::GetExternalPackageName(const ISceneOutlinerTreeItem& TreeItem) { if (const FActorTreeItem* ActorItem = TreeItem.CastTo()) { if (AActor* Actor = ActorItem->Actor.Get()) { if (Actor->IsPackageExternal()) { return Actor->GetExternalPackage()->GetName(); } } } else if (const FActorFolderTreeItem* ActorFolderItem = TreeItem.CastTo()) { if (const UActorFolder* ActorFolder = ActorFolderItem->GetActorFolder()) { if (ActorFolder->IsPackageExternal()) { return ActorFolder->GetExternalPackage()->GetName(); } } } else if (const FActorDescTreeItem* ActorDescItem = TreeItem.CastTo()) { if (const FWorldPartitionActorDescInstance* ActorDescInstance = *ActorDescItem->ActorDescHandle) { return ActorDescInstance->GetActorPackage().ToString(); } } return FString(); } UPackage* FSceneOutlinerHelpers::GetExternalPackage(const ISceneOutlinerTreeItem& TreeItem) { if (const FActorTreeItem* ActorItem = TreeItem.CastTo()) { if (AActor* Actor = ActorItem->Actor.Get()) { if (Actor->IsPackageExternal()) { return Actor->GetExternalPackage(); } } } else if (const FActorFolderTreeItem* ActorFolderItem = TreeItem.CastTo()) { if (const UActorFolder* ActorFolder = ActorFolderItem->GetActorFolder()) { if (ActorFolder->IsPackageExternal()) { return ActorFolder->GetExternalPackage(); } } } else if (const FActorDescTreeItem* ActorDescItem = TreeItem.CastTo()) { if (const FWorldPartitionActorDescInstance* ActorDescInstance = *ActorDescItem->ActorDescHandle) { return FindPackage(nullptr, *ActorDescInstance->GetActorPackage().ToString()); } } return nullptr; } TSharedPtr FSceneOutlinerHelpers::GetClassHyperlink(UObject* InObject) { if (InObject) { if (UClass* Class = InObject->GetClass()) { // Always show blueprints const bool bIsBlueprintClass = UBlueprint::GetBlueprintFromClass(Class) != nullptr; // Also show game or game plugin native classes (but not engine classes as that makes the scene outliner pretty noisy) bool bIsGameClass = false; if (!bIsBlueprintClass) { UPackage* Package = Class->GetOutermost(); const FString ModuleName = FPackageName::GetShortName(Package->GetFName()); FModuleStatus PackageModuleStatus; if (FModuleManager::Get().QueryModule(*ModuleName, /*out*/ PackageModuleStatus)) { bIsGameClass = PackageModuleStatus.bIsGameModule; } } if (bIsBlueprintClass || bIsGameClass) { FEditorClassUtils::FSourceLinkParams SourceLinkParams; SourceLinkParams.Object = InObject; SourceLinkParams.bUseDefaultFormat = true; return FEditorClassUtils::GetSourceLink(Class, SourceLinkParams); } } } return nullptr; } void FSceneOutlinerHelpers::PopulateExtraSearchStrings(const ISceneOutlinerTreeItem& TreeItem, TArray< FString >& OutSearchStrings) { // For components, we want them to be searchable by the actor name if they request so. This is so you can search by actors in component // pickers without the actual components themselves being filtered out. if (const FComponentTreeItem* ComponentTreeItem = TreeItem.CastTo()) { if (ComponentTreeItem->GetSearchComponentByActorName()) { if (const UActorComponent* Component = ComponentTreeItem->Component.Get()) { if (const AActor* Owner = Component->GetOwner()) { constexpr bool bCreateIfNone = false; OutSearchStrings.Add(Owner->GetActorLabel(bCreateIfNone)); } } } } } void FSceneOutlinerHelpers::RenameFolder(const FFolder& InFolder, const FText& NewFolderName, UWorld* World) { if (!World) { return; } FName NewPath = InFolder.GetParent().GetPath(); if (NewPath.IsNone()) { NewPath = FName(*NewFolderName.ToString()); } else { NewPath = FName(*(NewPath.ToString() / NewFolderName.ToString())); } const FFolder TreeItemNewFolder(InFolder.GetRootObject(), NewPath); FActorFolders::Get().RenameFolderInWorld(*World, InFolder, TreeItemNewFolder); } bool FSceneOutlinerHelpers::ValidateFolderName(const FFolder& InFolder, UWorld* World, const FText& InLabel, FText& OutErrorMessage) { const FText TrimmedLabel = FText::TrimPrecedingAndTrailing(InLabel); if (TrimmedLabel.IsEmpty()) { OutErrorMessage = LOCTEXT("RenameFailed_LeftBlank", "Names cannot be left blank"); return false; } if (TrimmedLabel.ToString().Len() >= NAME_SIZE) { FFormatNamedArguments Arguments; Arguments.Add(TEXT("CharCount"), NAME_SIZE); OutErrorMessage = FText::Format(LOCTEXT("RenameFailed_TooLong", "Names must be less than {CharCount} characters long."), Arguments); return false; } const FString LabelString = TrimmedLabel.ToString(); if (InFolder.GetLeafName().ToString() == LabelString) { return true; } int32 Dummy = 0; if (LabelString.FindChar('/', Dummy) || LabelString.FindChar('\\', Dummy)) { OutErrorMessage = LOCTEXT("RenameFailed_InvalidChar", "Folder names cannot contain / or \\."); return false; } // Validate that this folder doesn't exist already FName NewPath = InFolder.GetParent().GetPath(); if (NewPath.IsNone()) { NewPath = FName(*LabelString); } else { NewPath = FName(*(NewPath.ToString() / LabelString)); } FFolder NewFolder(InFolder.GetRootObject(), NewPath); if (World && FActorFolders::Get().ContainsFolder(*World, NewFolder)) { OutErrorMessage = LOCTEXT("RenameFailed_AlreadyExists", "A folder with this name already exists at this level"); return false; } return true; } bool FSceneOutlinerHelpers::IsFolderCurrent(const FFolder& InFolder, UWorld* World) { if (World) { return FActorFolders::Get().GetActorEditorContextFolder(*World) == InFolder; } return false; } } #undef LOCTEXT_NAMESPACE