// Copyright Epic Games, Inc. All Rights Reserved. #include "ActorBrowsingMode.h" #include "SceneOutlinerFilters.h" #include "SceneOutlinerModule.h" #include "SceneOutlinerMenuContext.h" #include "ActorHierarchy.h" #include "SceneOutlinerDelegates.h" #include "Editor.h" #include "Editor/GroupActor.h" #include "UnrealEdGlobals.h" #include "ToolMenus.h" #include "Engine/Selection.h" #include "Editor/UnrealEdEngine.h" #include "HAL/PlatformApplicationMisc.h" #include "SceneOutlinerDragDrop.h" #include "EditorActorFolders.h" #include "EditorFolderUtils.h" #include "ActorEditorUtils.h" #include "DragAndDrop/ActorDragDropOp.h" #include "DragAndDrop/ActorDragDropGraphEdOp.h" #include "DragAndDrop/FolderDragDropOp.h" #include "Logging/MessageLog.h" #include "SSocketChooser.h" #include "ActorFolderPickingMode.h" #include "ActorTreeItem.h" #include "LevelTreeItem.h" #include "ActorFolderTreeItem.h" #include "WorldTreeItem.h" #include "ComponentTreeItem.h" #include "ActorBrowsingModeSettings.h" #include "ActorDescTreeItem.h" #include "ScopedTransaction.h" #include "LevelInstance/LevelInstanceInterface.h" #include "LevelInstance/LevelInstanceSubsystem.h" #include "LevelInstance/LevelInstanceEditorInstanceActor.h" #include "EditorLevelUtils.h" DEFINE_LOG_CATEGORY_STATIC(LogActorBrowser, Log, All); #define LOCTEXT_NAMESPACE "SceneOutliner_ActorBrowsingMode" using FActorFilter = TSceneOutlinerPredicateFilter; using FActorDescFilter = TSceneOutlinerPredicateFilter; FActorBrowsingMode::FActorBrowsingMode(SSceneOutliner* InSceneOutliner, TWeakObjectPtr InSpecifiedWorldToDisplay) : FActorModeInteractive(FActorModeParams(InSceneOutliner, InSpecifiedWorldToDisplay, /* bHideComponents */ true, /* bHideLevelInstanceHierarchy */ false, /* bHideUnloadedActors */ false)) , FilteredActorCount(0) { // Capture selection changes of bones from mesh selection in fracture tools FSceneOutlinerDelegates::Get().OnComponentsUpdated.AddRaw(this, &FActorBrowsingMode::OnComponentsUpdated); GEngine->OnLevelActorDeleted().AddRaw(this, &FActorBrowsingMode::OnLevelActorDeleted); GEditor->OnSelectUnloadedActorsEvent().AddRaw(this, &FActorBrowsingMode::OnSelectUnloadedActors); FEditorDelegates::OnEditCutActorsBegin.AddRaw(this, &FActorBrowsingMode::OnEditCutActorsBegin); FEditorDelegates::OnEditCutActorsEnd.AddRaw(this, &FActorBrowsingMode::OnEditCutActorsEnd); FEditorDelegates::OnEditCopyActorsBegin.AddRaw(this, &FActorBrowsingMode::OnEditCopyActorsBegin); FEditorDelegates::OnEditCopyActorsEnd.AddRaw(this, &FActorBrowsingMode::OnEditCopyActorsEnd); FEditorDelegates::OnEditPasteActorsBegin.AddRaw(this, &FActorBrowsingMode::OnEditPasteActorsBegin); FEditorDelegates::OnEditPasteActorsEnd.AddRaw(this, &FActorBrowsingMode::OnEditPasteActorsEnd); FEditorDelegates::OnDuplicateActorsBegin.AddRaw(this, &FActorBrowsingMode::OnDuplicateActorsBegin); FEditorDelegates::OnDuplicateActorsEnd.AddRaw(this, &FActorBrowsingMode::OnDuplicateActorsEnd); FEditorDelegates::OnDeleteActorsBegin.AddRaw(this, &FActorBrowsingMode::OnDeleteActorsBegin); FEditorDelegates::OnDeleteActorsEnd.AddRaw(this, &FActorBrowsingMode::OnDeleteActorsEnd); UActorBrowserConfig::Initialize(); UActorBrowserConfig::Get()->LoadEditorConfig(); // Get a MutableConfig here to force create a config for the current outliner if it doesn't exist const FActorBrowsingModeConfig* SavedSettings = GetMutableConfig(); // Create a local struct to use the default values if this outliner doesn't want to save configs FActorBrowsingModeConfig LocalSettings; // If this outliner doesn't want to save config (OutlinerIdentifier is empty, use the defaults) if (SavedSettings) { LocalSettings = *SavedSettings; } // Get the OutlinerModule to register FilterInfos with the FilterInfoMap FSceneOutlinerFilterInfo ShowOnlySelectedActorsInfo(LOCTEXT("ToggleShowOnlySelected", "Only Selected"), LOCTEXT("ToggleShowOnlySelectedToolTip", "When enabled, only displays actors that are currently selected."), LocalSettings.bShowOnlySelectedActors, FCreateSceneOutlinerFilter::CreateStatic(&FActorBrowsingMode::CreateShowOnlySelectedActorsFilter)); ShowOnlySelectedActorsInfo.OnToggle().AddLambda([this](bool bIsActive) { FActorBrowsingModeConfig* Settings = GetMutableConfig(); if(Settings) { Settings->bShowOnlySelectedActors = bIsActive; SaveConfig(); } }); FilterInfoMap.Add(TEXT("ShowOnlySelectedActors"), ShowOnlySelectedActorsInfo); FSceneOutlinerFilterInfo HideTemporaryActorsInfo(LOCTEXT("ToggleHideTemporaryActors", "Hide Temporary Actors"), LOCTEXT("ToggleHideTemporaryActorsToolTip", "When enabled, hides temporary/run-time Actors."), LocalSettings.bHideTemporaryActors, FCreateSceneOutlinerFilter::CreateStatic(&FActorBrowsingMode::CreateHideTemporaryActorsFilter)); HideTemporaryActorsInfo.OnToggle().AddLambda([this](bool bIsActive) { FActorBrowsingModeConfig* Settings = GetMutableConfig(); if(Settings) { Settings->bHideTemporaryActors = bIsActive; SaveConfig(); } }); FilterInfoMap.Add(TEXT("HideTemporaryActors"), HideTemporaryActorsInfo); FSceneOutlinerFilterInfo OnlyCurrentLevelInfo(LOCTEXT("ToggleShowOnlyCurrentLevel", "Only in Current Level"), LOCTEXT("ToggleShowOnlyCurrentLevelToolTip", "When enabled, only shows Actors that are in the Current Level."), LocalSettings.bShowOnlyActorsInCurrentLevel, FCreateSceneOutlinerFilter::CreateStatic(&FActorBrowsingMode::CreateIsInCurrentLevelFilter)); OnlyCurrentLevelInfo.OnToggle().AddLambda([this](bool bIsActive) { FActorBrowsingModeConfig* Settings = GetMutableConfig(); if(Settings) { Settings->bShowOnlyActorsInCurrentLevel = bIsActive; SaveConfig(); } }); FilterInfoMap.Add(TEXT("ShowOnlyCurrentLevel"), OnlyCurrentLevelInfo); bHideComponents = LocalSettings.bHideActorComponents; FSceneOutlinerFilterInfo HideComponentsInfo(LOCTEXT("ToggleHideActorComponents", "Hide Actor Components"), LOCTEXT("ToggleHideActorComponentsToolTip", "When enabled, hides components belonging to actors."), LocalSettings.bHideActorComponents, FCreateSceneOutlinerFilter::CreateStatic(&FActorBrowsingMode::CreateHideComponentsFilter)); HideComponentsInfo.OnToggle().AddLambda([this](bool bIsActive) { FActorBrowsingModeConfig* Settings = GetMutableConfig(); if(Settings) { Settings->bHideActorComponents = bHideComponents = bIsActive; SaveConfig(); } if (auto ActorHierarchy = StaticCast(Hierarchy.Get())) { ActorHierarchy->SetShowingComponents(!bIsActive); } }); FilterInfoMap.Add(TEXT("HideComponentsFilter"), HideComponentsInfo); FSceneOutlinerFilterInfo HideLevelInstancesInfo(LOCTEXT("ToggleHideLevelInstanceContent", "Hide Level Instance Content"), LOCTEXT("ToggleHideLevelInstancesToolTip", "When enabled, hides all level instance content."), LocalSettings.bHideLevelInstanceHierarchy, FCreateSceneOutlinerFilter::CreateStatic(&FActorBrowsingMode::CreateHideLevelInstancesFilter)); HideLevelInstancesInfo.OnToggle().AddLambda([this](bool bIsActive) { FActorBrowsingModeConfig* Settings = GetMutableConfig(); if(Settings) { Settings->bHideLevelInstanceHierarchy = bHideLevelInstanceHierarchy = bIsActive; SaveConfig(); } if (auto ActorHierarchy = StaticCast(Hierarchy.Get())) { ActorHierarchy->SetShowingLevelInstances(!bIsActive); } }); FilterInfoMap.Add(TEXT("HideLevelInstancesFilter"), HideLevelInstancesInfo); FSceneOutlinerFilterInfo HideUnloadedActorsInfo(LOCTEXT("ToggleHideUnloadedActors", "Hide Unloaded Actors"), LOCTEXT("ToggleHideUnloadedActorsToolTip", "When enabled, hides all unloaded world partition actors."), LocalSettings.bHideUnloadedActors, FCreateSceneOutlinerFilter::CreateStatic(&FActorBrowsingMode::CreateHideUnloadedActorsFilter)); HideUnloadedActorsInfo.OnToggle().AddLambda([this] (bool bIsActive) { FActorBrowsingModeConfig* Settings = GetMutableConfig(); if(Settings) { Settings->bHideUnloadedActors = bHideUnloadedActors = bIsActive; SaveConfig(); } if (auto ActorHierarchy = StaticCast(Hierarchy.Get())) { ActorHierarchy->SetShowingUnloadedActors(!bIsActive); } }); FilterInfoMap.Add(TEXT("HideUnloadedActorsFilter"), HideUnloadedActorsInfo); // Add a filter which sets the interactive mode of LevelInstance items and their children SceneOutliner->AddFilter(MakeShared(FActorTreeItem::FFilterPredicate::CreateStatic([](const AActor* Actor) {return true; }), FSceneOutlinerFilter::EDefaultBehaviour::Pass, FActorTreeItem::FFilterPredicate::CreateLambda([this](const AActor* Actor) { if (!bHideLevelInstanceHierarchy) { if (const ULevelInstanceSubsystem* LevelInstanceSubsystem = RepresentingWorld->GetSubsystem()) { // if actor has a valid parent and the parent is not being edited, // then the actor should not be selectable. if (const ILevelInstanceInterface* ParentLevelInstance = LevelInstanceSubsystem->GetParentLevelInstance(Actor)) { if (!LevelInstanceSubsystem->IsEditingLevelInstance(ParentLevelInstance)) { return false; } } } } return true; }))); bAlwaysFrameSelection = LocalSettings.bAlwaysFrameSelection; Rebuild(); } FActorBrowsingMode::~FActorBrowsingMode() { if (RepresentingWorld.IsValid()) { if (UWorldPartition* const WorldPartition = RepresentingWorld->GetWorldPartition()) { WorldPartition->OnActorDescRemovedEvent.RemoveAll(this); } } FSceneOutlinerDelegates::Get().OnComponentsUpdated.RemoveAll(this); GEngine->OnLevelActorDeleted().RemoveAll(this); GEditor->OnSelectUnloadedActorsEvent().RemoveAll(this); FEditorDelegates::OnEditCutActorsBegin.RemoveAll(this); FEditorDelegates::OnEditCutActorsEnd.RemoveAll(this); FEditorDelegates::OnEditCopyActorsBegin.RemoveAll(this); FEditorDelegates::OnEditCopyActorsEnd.RemoveAll(this); FEditorDelegates::OnEditPasteActorsBegin.RemoveAll(this); FEditorDelegates::OnEditPasteActorsEnd.RemoveAll(this); FEditorDelegates::OnDuplicateActorsBegin.RemoveAll(this); FEditorDelegates::OnDuplicateActorsEnd.RemoveAll(this); FEditorDelegates::OnDeleteActorsBegin.RemoveAll(this); FEditorDelegates::OnDeleteActorsEnd.RemoveAll(this); } void FActorBrowsingMode::Rebuild() { // If we used to be representing a wp world, unbind delegates before rebuilding begins if (RepresentingWorld.IsValid()) { if (UWorldPartition* const WorldPartition = RepresentingWorld->GetWorldPartition()) { WorldPartition->OnActorDescRemovedEvent.RemoveAll(this); } } FActorMode::Rebuild(); FilteredActorCount = 0; FilteredUnloadedActorCount = 0; ApplicableUnloadedActors.Empty(); ApplicableActors.Empty(); bRepresentingWorldGameWorld = RepresentingWorld.IsValid() && RepresentingWorld->IsGameWorld(); bRepresentingWorldPartitionedWorld = RepresentingWorld.IsValid() && RepresentingWorld->IsPartitionedWorld(); if (bRepresentingWorldPartitionedWorld) { UWorldPartition* const WorldPartition = RepresentingWorld->GetWorldPartition(); WorldPartition->OnActorDescRemovedEvent.AddRaw(this, &FActorBrowsingMode::OnActorDescRemoved); } } FText FActorBrowsingMode::GetStatusText() const { if (!RepresentingWorld.IsValid()) { return FText(); } // The number of actors in the outliner before applying the text filter const int32 TotalActorCount = ApplicableActors.Num() + ApplicableUnloadedActors.Num(); const int32 SelectedActorCount = SceneOutliner->GetSelection().Num(); if (!SceneOutliner->IsTextFilterActive()) { if (SelectedActorCount == 0) //-V547 { if (bRepresentingWorldPartitionedWorld) { return FText::Format(LOCTEXT("ShowingAllLoadedActorsFmt", "{0} actors ({1} loaded)"), FText::AsNumber(FilteredActorCount), FText::AsNumber(FilteredActorCount - FilteredUnloadedActorCount)); } else { return FText::Format(LOCTEXT("ShowingAllActorsFmt", "{0} actors"), FText::AsNumber(FilteredActorCount)); } } else { return FText::Format(LOCTEXT("ShowingAllActorsSelectedFmt", "{0} actors ({1} selected)"), FText::AsNumber(FilteredActorCount), FText::AsNumber(SelectedActorCount)); } } else if (SceneOutliner->IsTextFilterActive() && FilteredActorCount == 0) { return FText::Format(LOCTEXT("ShowingNoActorsFmt", "No matching actors ({0} total)"), FText::AsNumber(TotalActorCount)); } else if (SelectedActorCount != 0) //-V547 { return FText::Format(LOCTEXT("ShowingOnlySomeActorsSelectedFmt", "Showing {0} of {1} actors ({2} selected)"), FText::AsNumber(FilteredActorCount), FText::AsNumber(TotalActorCount), FText::AsNumber(SelectedActorCount)); } else { return FText::Format(LOCTEXT("ShowingOnlySomeActorsFmt", "Showing {0} of {1} actors"), FText::AsNumber(FilteredActorCount), FText::AsNumber(TotalActorCount)); } } FSlateColor FActorBrowsingMode::GetStatusTextColor() const { if (!SceneOutliner->IsTextFilterActive()) { return FSlateColor::UseForeground(); } else if (FilteredActorCount == 0) { return FAppStyle::Get().GetSlateColor("Colors.AccentRed"); } else { return FAppStyle::Get().GetSlateColor("Colors.AccentGreen"); } } void FActorBrowsingMode::OnToggleAlwaysFrameSelection() { bAlwaysFrameSelection = !bAlwaysFrameSelection; FActorBrowsingModeConfig* Settings = GetMutableConfig(); if(Settings) { Settings->bAlwaysFrameSelection = bAlwaysFrameSelection; SaveConfig(); } } bool FActorBrowsingMode::ShouldAlwaysFrameSelection() { return bAlwaysFrameSelection; } void FActorBrowsingMode::CreateViewContent(FMenuBuilder& MenuBuilder) { MenuBuilder.BeginSection("OutlinerSelectionOptions", LOCTEXT("OptionsHeading", "Options")); MenuBuilder.AddMenuEntry( LOCTEXT("AlwaysFrameSelection", "Always Frame Selection"), LOCTEXT("AlwaysFrameSelectionTooltip", "Select an item in the outliner when it is selected through the level editor viewport."), FSlateIcon(), FUIAction( FExecuteAction::CreateRaw(this, &FActorBrowsingMode::OnToggleAlwaysFrameSelection), FCanExecuteAction(), FIsActionChecked::CreateRaw(this, &FActorBrowsingMode::ShouldAlwaysFrameSelection) ), NAME_None, EUserInterfaceActionType::ToggleButton ); MenuBuilder.EndSection(); MenuBuilder.BeginSection("AssetThumbnails", LOCTEXT("ShowWorldHeading", "World")); { MenuBuilder.AddSubMenu( LOCTEXT("ChooseWorldSubMenu", "Choose World"), LOCTEXT("ChooseWorldSubMenuToolTip", "Choose the world to display in the outliner."), FNewMenuDelegate::CreateRaw(this, &FActorMode::BuildWorldPickerMenu) ); } MenuBuilder.EndSection(); } TSharedRef FActorBrowsingMode::CreateShowOnlySelectedActorsFilter() { auto IsActorSelected = [](const AActor* InActor) { return InActor && InActor->IsSelected(); }; return MakeShareable(new FActorFilter(FActorTreeItem::FFilterPredicate::CreateStatic(IsActorSelected), FSceneOutlinerFilter::EDefaultBehaviour::Fail, FActorTreeItem::FFilterPredicate::CreateStatic(IsActorSelected))); } TSharedRef FActorBrowsingMode::CreateHideTemporaryActorsFilter() { return MakeShareable(new FActorFilter(FActorTreeItem::FFilterPredicate::CreateStatic([](const AActor* InActor) { return ((InActor->GetWorld() && InActor->GetWorld()->WorldType != EWorldType::PIE) || GEditor->ObjectsThatExistInEditorWorld.Get(InActor)) && !InActor->HasAnyFlags(EObjectFlags::RF_Transient); }), FSceneOutlinerFilter::EDefaultBehaviour::Pass)); } TSharedRef FActorBrowsingMode::CreateIsInCurrentLevelFilter() { return MakeShareable(new FActorFilter(FActorTreeItem::FFilterPredicate::CreateStatic([](const AActor* InActor) { if (InActor->GetWorld()) { return InActor->GetLevel() == InActor->GetWorld()->GetCurrentLevel(); } return false; }), FSceneOutlinerFilter::EDefaultBehaviour::Pass)); } TSharedRef FActorBrowsingMode::CreateHideComponentsFilter() { return MakeShared>(TSceneOutlinerPredicateFilter( FComponentTreeItem::FFilterPredicate::CreateStatic([](const UActorComponent*) { return false; }), FSceneOutlinerFilter::EDefaultBehaviour::Pass)); } TSharedRef FActorBrowsingMode::CreateHideLevelInstancesFilter() { return MakeShareable(new FActorFilter(FActorTreeItem::FFilterPredicate::CreateStatic([](const AActor* Actor) { // Check if actor belongs to a LevelInstance if (const ULevelInstanceSubsystem* LevelInstanceSubsystem = Actor->GetWorld()->GetSubsystem()) { if (const ILevelInstanceInterface* ParentLevelInstance = LevelInstanceSubsystem->GetParentLevelInstance(Actor)) { if (!LevelInstanceSubsystem->IsEditingLevelInstance(ParentLevelInstance)) { return false; } } } // Or if the actor itself is a LevelInstance editor instance return Cast(Actor) == nullptr; }), FSceneOutlinerFilter::EDefaultBehaviour::Pass)); } TSharedRef FActorBrowsingMode::CreateHideUnloadedActorsFilter() { return MakeShareable(new FActorDescFilter(FActorDescTreeItem::FFilterPredicate::CreateStatic( [](const FWorldPartitionActorDesc* ActorDesc) { return false; }), FSceneOutlinerFilter::EDefaultBehaviour::Pass)); } static const FName DefaultContextBaseMenuName("SceneOutliner.DefaultContextMenuBase"); static const FName DefaultContextMenuName("SceneOutliner.DefaultContextMenu"); void FActorBrowsingMode::RegisterContextMenu() { UToolMenus* ToolMenus = UToolMenus::Get(); if (!ToolMenus->IsMenuRegistered(DefaultContextBaseMenuName)) { UToolMenu* Menu = ToolMenus->RegisterMenu(DefaultContextBaseMenuName); Menu->AddDynamicSection("DynamicHierarchySection", FNewToolMenuDelegate::CreateLambda([](UToolMenu* InMenu) { USceneOutlinerMenuContext* Context = InMenu->FindContext(); if (!Context || !Context->SceneOutliner.IsValid()) { return; } // NOTE: the name "Section" is used in many other places FToolMenuSection& Section = InMenu->FindOrAddSection("Section"); Section.Label = LOCTEXT("HierarchySectionName", "Hierarchy"); SSceneOutliner* SceneOutliner = Context->SceneOutliner.Pin().Get(); if (Context->bShowParentTree) { if (Context->NumSelectedItems == 0) { Section.AddMenuEntry( "CreateFolder", LOCTEXT("CreateFolder", "Create Folder"), FText(), FSlateIcon(FAppStyle::GetAppStyleSetName(), "SceneOutliner.NewFolderIcon"), FUIAction(FExecuteAction::CreateSP(SceneOutliner, &SSceneOutliner::CreateFolder))); } else { if (Context->NumSelectedItems == 1) { SceneOutliner->GetTree().GetSelectedItems()[0]->GenerateContextMenu(InMenu, *SceneOutliner); } // If we've only got folders selected, show the selection and edit sub menus if (Context->NumSelectedItems > 0 && Context->NumSelectedFolders == Context->NumSelectedItems) { Section.AddSubMenu( "SelectSubMenu", LOCTEXT("SelectSubmenu", "Select"), LOCTEXT("SelectSubmenu_Tooltip", "Select the contents of the current selection"), FNewToolMenuDelegate::CreateSP(SceneOutliner, &SSceneOutliner::FillSelectionSubMenu)); } } } })); Menu->AddDynamicSection("DynamicMainSection", FNewToolMenuDelegate::CreateLambda([](UToolMenu* InMenu) { // We always create a section here, even if there is no parent so that clients can still extend the menu FToolMenuSection& Section = InMenu->AddSection("MainSection", LOCTEXT("OutlinerSectionName", "Outliner")); if (USceneOutlinerMenuContext* Context = InMenu->FindContext()) { // Don't add any of these menu items if we're not showing the parent tree // Can't move worlds or level blueprints if (Context->bShowParentTree && Context->NumSelectedItems > 0 && Context->NumWorldsSelected == 0 && Context->SceneOutliner.IsValid()) { Section.AddSubMenu( "MoveActorsTo", LOCTEXT("MoveActorsTo", "Move To"), LOCTEXT("MoveActorsTo_Tooltip", "Move selection to another folder"), FNewToolMenuDelegate::CreateSP(Context->SceneOutliner.Pin().Get(), &SSceneOutliner::FillFoldersSubMenu)); } if (Context->bShowParentTree && Context->NumSelectedItems > 0 && Context->SceneOutliner.IsValid()) { // Only add the menu option to wp levels if (!Context->bRepresentingGameWorld && Context->bRepresentingPartitionedWorld) { // If selection contains some unpinned items, show the pin option // If the selection contains folders, always show the pin option if (Context->NumPinnedItems != Context->NumSelectedItems || Context->NumSelectedFolders > 0) { Section.AddMenuEntry( "PinItems", LOCTEXT("Pin", "Pin"), FText(), FSlateIcon(), FUIAction(FExecuteAction::CreateSP(Context->SceneOutliner.Pin().Get(), &SSceneOutliner::PinSelectedItems))); } // If the selection contains some pinned items, show the unpin option // If the selection contains folders, always show the unpin option if (Context->NumPinnedItems != 0 || Context->NumSelectedFolders > 0) { Section.AddMenuEntry( "UnpinItems", LOCTEXT("Unpin", "Unpin"), FText(), FSlateIcon(), FUIAction(FExecuteAction::CreateSP(Context->SceneOutliner.Pin().Get(), &SSceneOutliner::UnpinSelectedItems))); } } } } })); Menu->AddDynamicSection("DynamicActorEditorContext", FNewToolMenuDelegate::CreateLambda([this](UToolMenu* InMenu) { USceneOutlinerMenuContext* Context = InMenu->FindContext(); if (Context && Context->SceneOutliner.IsValid() && Context->bShowParentTree) { FToolMenuSection& Section = InMenu->AddSection("ActorEditorContextSection", LOCTEXT("ActorEditorContextSectionName", "Actor Editor Context")); SSceneOutliner* SceneOutliner = Context->SceneOutliner.Pin().Get(); if ((Context->NumSelectedItems == 1) && (Context->NumSelectedFolders == 1)) { SceneOutliner->GetTree().GetSelectedItems()[0]->GenerateContextMenu(InMenu, *SceneOutliner); Section.AddMenuEntry( "MakeCurrentFolder", LOCTEXT("MakeCurrentFolder", "Make Current Folder"), FText(), FSlateIcon(), FUIAction( FExecuteAction::CreateLambda([SceneOutliner]() { const FSceneOutlinerItemSelection& Selection = SceneOutliner->GetSelection(); if (Selection.SelectedItems.Num() == 1) { FSceneOutlinerTreeItemPtr Item = Selection.SelectedItems[0].Pin(); if (FActorFolderTreeItem* FolderItem = Item->CastTo()) { if (FolderItem->World.IsValid()) { const FScopedTransaction Transaction(NSLOCTEXT("UnrealEd", "MakeCurrentActorFolder", "Make Current Actor Folder")); FActorFolders::Get().SetActorEditorContextFolder(*FolderItem->World, FolderItem->GetFolder()); } } } }), FCanExecuteAction::CreateLambda([SceneOutliner] { const FSceneOutlinerItemSelection& Selection = SceneOutliner->GetSelection(); if (Selection.SelectedItems.Num() == 1) { FSceneOutlinerTreeItemPtr Item = Selection.SelectedItems[0].Pin(); if (FActorFolderTreeItem* FolderItem = Item->CastTo()) { return FolderItem->World.IsValid() && (FolderItem->World->GetCurrentLevel() == FolderItem->GetFolder().GetRootObjectAssociatedLevel()); } } return false; }) ) ); } Section.AddMenuEntry( "ClearCurrentFolder", LOCTEXT("ClearCurrentFolder", "Clear Current Folder"), FText(), FSlateIcon(), FUIAction( FExecuteAction::CreateLambda([this, SceneOutliner]() { if (RepresentingWorld.IsValid()) { const FScopedTransaction Transaction(NSLOCTEXT("UnrealEd", "ClearCurrentActorFolder", "Clear Current Actor Folder")); FActorFolders::Get().SetActorEditorContextFolder(*RepresentingWorld.Get(), FFolder::GetWorldRootFolder(RepresentingWorld.Get())); } }), FCanExecuteAction::CreateLambda([this] { return RepresentingWorld.IsValid() && !FActorFolders::Get().GetActorEditorContextFolder(*RepresentingWorld.Get()).IsNone(); }) ) ); } })); } if (!ToolMenus->IsMenuRegistered(DefaultContextMenuName)) { ToolMenus->RegisterMenu(DefaultContextMenuName, DefaultContextBaseMenuName); } } TSharedPtr FActorBrowsingMode::BuildContextMenu() { RegisterContextMenu(); const FSceneOutlinerItemSelection ItemSelection(SceneOutliner->GetSelection()); USceneOutlinerMenuContext* ContextObject = NewObject(); ContextObject->SceneOutliner = StaticCastSharedRef(SceneOutliner->AsShared()); ContextObject->bShowParentTree = SceneOutliner->GetSharedData().bShowParentTree; ContextObject->NumSelectedItems = ItemSelection.Num(); ContextObject->NumSelectedFolders = ItemSelection.Num(); ContextObject->NumWorldsSelected = ItemSelection.Num(); ContextObject->bRepresentingGameWorld = bRepresentingWorldGameWorld; ContextObject->bRepresentingPartitionedWorld = bRepresentingWorldPartitionedWorld; int32 NumPinnedItems = 0; if (const UWorldPartition* const WorldPartition = RepresentingWorld->GetWorldPartition()) { ItemSelection.ForEachItem([WorldPartition, &NumPinnedItems](const IActorBaseTreeItem& ActorItem) { if (WorldPartition->IsActorPinned(ActorItem.GetGuid())) { ++NumPinnedItems; } return true; }); } ContextObject->NumPinnedItems = NumPinnedItems; FToolMenuContext Context(ContextObject); FName MenuName = DefaultContextMenuName; SceneOutliner->GetSharedData().ModifyContextMenu.ExecuteIfBound(MenuName, Context); // Build up the menu for a selection UToolMenus* ToolMenus = UToolMenus::Get(); UToolMenu* Menu = ToolMenus->GenerateMenu(MenuName, Context); for (const FToolMenuSection& Section : Menu->Sections) { if (Section.Blocks.Num() > 0) { return ToolMenus->GenerateWidget(Menu); } } return nullptr; } TSharedPtr FActorBrowsingMode::CreateContextMenu() { TArray SelectedActors; GEditor->GetSelectedActors()->GetSelectedObjects(SelectedActors); // Make sure that no components are selected if (GEditor->GetSelectedComponentCount() > 0) { // We want to be able to undo to regain the previous component selection const FScopedTransaction Transaction(NSLOCTEXT("UnrealEd", "ClickingOnActorsContextMenu", "Clicking on Actors (context menu)")); USelection* ComponentSelection = GEditor->GetSelectedComponents(); ComponentSelection->Modify(false); ComponentSelection->DeselectAll(); GUnrealEd->UpdatePivotLocationForSelection(); GEditor->RedrawLevelEditingViewports(false); } return BuildContextMenu(); } void FActorBrowsingMode::OnItemAdded(FSceneOutlinerTreeItemPtr Item) { if (const FActorTreeItem* ActorItem = Item->CastTo()) { if (!Item->Flags.bIsFilteredOut) { ++FilteredActorCount; // Synchronize selection if (GEditor->GetSelectedActors()->IsSelected(ActorItem->Actor.Get())) { SceneOutliner->SetItemSelection(Item, true); } } } else if (FActorFolderTreeItem* FolderItem = Item->CastTo()) { if (FolderItem->World.IsValid()) { FolderItem->Flags.bIsExpanded = FActorFolders::Get().IsFolderExpanded(*FolderItem->World, FolderItem->GetFolder()); } } else if (Item->IsA()) { if (!Item->Flags.bIsFilteredOut) { ++FilteredActorCount; ++FilteredUnloadedActorCount; } } } void FActorBrowsingMode::OnItemRemoved(FSceneOutlinerTreeItemPtr Item) { if (Item->IsA()) { if (!Item->Flags.bIsFilteredOut) { --FilteredActorCount; } } else if (Item->IsA()) { if (!Item->Flags.bIsFilteredOut) { --FilteredActorCount; --FilteredUnloadedActorCount; } } } void FActorBrowsingMode::OnComponentsUpdated() { SceneOutliner->FullRefresh(); } void FActorBrowsingMode::OnLevelActorDeleted(AActor* Actor) { ApplicableActors.Remove(Actor); } void FActorBrowsingMode::OnSelectUnloadedActors(const TArray& ActorGuids) { TArray ItemsToSelect; ItemsToSelect.Reserve(ActorGuids.Num()); for (const FGuid& ActorGuid : ActorGuids) { if (FSceneOutlinerTreeItemPtr ItemPtr = SceneOutliner->GetTreeItem(ActorGuid)) { ItemsToSelect.Add(ItemPtr); } } if (ItemsToSelect.Num()) { SceneOutliner->SetItemSelection(ItemsToSelect, true); } } void FActorBrowsingMode::OnActorDescRemoved(FWorldPartitionActorDesc* InActorDesc) { ApplicableUnloadedActors.Remove(InActorDesc); } void FActorBrowsingMode::OnItemSelectionChanged(FSceneOutlinerTreeItemPtr TreeItem, ESelectInfo::Type SelectionType, const FSceneOutlinerItemSelection& Selection) { TArray SelectedActors = Selection.GetData(SceneOutliner::FActorSelector()); bool bChanged = false; bool bAnyInPIE = false; for (auto* Actor : SelectedActors) { if (!bAnyInPIE && Actor && Actor->GetOutermost()->HasAnyPackageFlags(PKG_PlayInEditor)) { bAnyInPIE = true; } if (!GEditor->GetSelectedActors()->IsSelected(Actor)) { bChanged = true; break; } } for (FSelectionIterator SelectionIt(*GEditor->GetSelectedActors()); SelectionIt && !bChanged; ++SelectionIt) { const AActor* Actor = CastChecked< AActor >(*SelectionIt); if (!bAnyInPIE && Actor->GetOutermost()->HasAnyPackageFlags(PKG_PlayInEditor)) { bAnyInPIE = true; } if (!SelectedActors.Contains(Actor)) { // Actor has been deselected bChanged = true; // If actor was a group actor, remove its members from the ActorsToSelect list const AGroupActor* DeselectedGroupActor = Cast(Actor); if (DeselectedGroupActor) { TArray GroupActors; DeselectedGroupActor->GetGroupActors(GroupActors); for (auto* GroupActor : GroupActors) { SelectedActors.Remove(GroupActor); } } } } // If there's a discrepancy, update the selected actors to reflect this list. if (bChanged) { const FScopedTransaction Transaction(NSLOCTEXT("UnrealEd", "ClickingOnActors", "Clicking on Actors"), !bAnyInPIE); GEditor->GetSelectedActors()->Modify(); // We'll batch selection changes instead by using BeginBatchSelectOperation() GEditor->GetSelectedActors()->BeginBatchSelectOperation(); // Clear the selection. GEditor->SelectNone(false, true, true); const bool bShouldSelect = true; const bool bNotifyAfterSelect = false; const bool bSelectEvenIfHidden = true; // @todo outliner: Is this actually OK? for (auto* Actor : SelectedActors) { UE_LOG(LogActorBrowser, Verbose, TEXT("Clicking on Actor (world outliner): %s (%s)"), *Actor->GetClass()->GetName(), *Actor->GetActorLabel()); GEditor->SelectActor(Actor, bShouldSelect, bNotifyAfterSelect, bSelectEvenIfHidden); } // Commit selection changes GEditor->GetSelectedActors()->EndBatchSelectOperation(/*bNotify*/false); // Fire selection changed event GEditor->NoteSelectionChange(); // Set this outliner as the most recently interacted with SetAsMostRecentOutliner(); } SceneOutliner->RefreshSelection(); } void FActorBrowsingMode::OnItemDoubleClick(FSceneOutlinerTreeItemPtr Item) { if (const FActorTreeItem* ActorItem = Item->CastTo()) { AActor* Actor = ActorItem->Actor.Get(); check(Actor); ILevelInstanceInterface* LevelInstance = Cast(Actor); if (LevelInstance && FSlateApplication::Get().GetModifierKeys().IsAltDown()) { if (LevelInstance->CanEnterEdit()) { LevelInstance->EnterEdit(); } else if (LevelInstance->CanExitEdit()) { LevelInstance->ExitEdit(); } } else if (Item->CanInteract()) { FSceneOutlinerItemSelection Selection(SceneOutliner->GetSelection()); if (Selection.Has()) { const bool bActiveViewportOnly = false; GEditor->MoveViewportCamerasToActor(Selection.GetData(SceneOutliner::FActorSelector()), bActiveViewportOnly); } } else { const bool bActiveViewportOnly = false; GEditor->MoveViewportCamerasToActor(*Actor, bActiveViewportOnly); } } } void FActorBrowsingMode::OnFilterTextCommited(FSceneOutlinerItemSelection& Selection, ETextCommit::Type CommitType) { // Start batching selection changes GEditor->GetSelectedActors()->BeginBatchSelectOperation(); // Select actors (and only the actors) that match the filter text const bool bNoteSelectionChange = false; const bool bDeselectBSPSurfs = false; const bool WarnAboutManyActors = true; GEditor->SelectNone(bNoteSelectionChange, bDeselectBSPSurfs, WarnAboutManyActors); for (AActor* Actor : Selection.GetData(SceneOutliner::FActorSelector())) { const bool bShouldSelect = true; const bool bSelectEvenIfHidden = false; GEditor->SelectActor(Actor, bShouldSelect, bNoteSelectionChange, bSelectEvenIfHidden); } // Commit selection changes GEditor->GetSelectedActors()->EndBatchSelectOperation(/*bNotify*/false); // Fire selection changed event GEditor->NoteSelectionChange(); // Set keyboard focus to the SceneOutliner, so the user can perform keyboard commands that interact // with selected actors (such as Delete, to delete selected actors.) SceneOutliner->SetKeyboardFocus(); SetAsMostRecentOutliner(); } void FActorBrowsingMode::OnItemPassesFilters(const ISceneOutlinerTreeItem& Item) { if (const FActorTreeItem* const ActorItem = Item.CastTo()) { ApplicableActors.Add(ActorItem->Actor); } else if (const FActorDescTreeItem* const ActorDescItem = Item.CastTo(); ActorDescItem && ActorDescItem->IsValid()) { ApplicableUnloadedActors.Add(ActorDescItem->ActorDescHandle.Get()); } } FReply FActorBrowsingMode::OnKeyDown(const FKeyEvent& InKeyEvent) { const FSceneOutlinerItemSelection& Selection = SceneOutliner->GetSelection(); // Rename key: Rename selected actors (not rebindable, because it doesn't make much sense to bind.) if (InKeyEvent.GetKey() == EKeys::F2) { if (Selection.Num() == 1) { FSceneOutlinerTreeItemPtr ItemToRename = Selection.SelectedItems[0].Pin(); if (ItemToRename.IsValid() && CanRenameItem(*ItemToRename) && ItemToRename->CanInteract()) { SceneOutliner->SetPendingRenameItem(ItemToRename); SceneOutliner->ScrollItemIntoView(ItemToRename); } return FReply::Handled(); } } // F5 forces a full refresh else if (InKeyEvent.GetKey() == EKeys::F5) { SceneOutliner->FullRefresh(); return FReply::Handled(); } // Delete key: Delete selected actors (not rebindable, because it doesn't make much sense to bind.) // Use Delete and Backspace instead of Platform_Delete because the LevelEditor default Edit Delete is bound to both else if (InKeyEvent.GetKey() == EKeys::Delete || InKeyEvent.GetKey() == EKeys::BackSpace) { if (SceneOutliner->GetSharedData().CustomDelete.IsBound()) { SceneOutliner->GetSharedData().CustomDelete.Execute(Selection.SelectedItems); } else { if (RepresentingWorld.IsValid()) { GUnrealEd->Exec(RepresentingWorld.Get(), TEXT("DELETE")); } } return FReply::Handled(); } return FReply::Unhandled(); } bool FActorBrowsingMode::CanDelete() const { const FSceneOutlinerItemSelection ItemSelection = SceneOutliner->GetSelection(); const uint32 NumberOfFolders = ItemSelection.Num(); return (NumberOfFolders > 0 && NumberOfFolders == ItemSelection.Num()); } bool FActorBrowsingMode::CanRename() const { const FSceneOutlinerItemSelection ItemSelection = SceneOutliner->GetSelection(); const uint32 NumberOfFolders = ItemSelection.Num(); return (NumberOfFolders == 1 && NumberOfFolders == ItemSelection.Num()); } bool FActorBrowsingMode::CanRenameItem(const ISceneOutlinerTreeItem& Item) const { // Can only rename actor and folder items when in actor browsing mode return (Item.IsValid() && (Item.IsA() || Item.IsA())); } bool FActorBrowsingMode::CanCut() const { const FSceneOutlinerItemSelection ItemSelection = SceneOutliner->GetSelection(); const uint32 NumberOfFolders = ItemSelection.Num(); return (NumberOfFolders > 0 && NumberOfFolders == ItemSelection.Num()); } bool FActorBrowsingMode::CanCopy() const { const FSceneOutlinerItemSelection ItemSelection = SceneOutliner->GetSelection(); const uint32 NumberOfFolders = ItemSelection.Num(); return (NumberOfFolders > 0 && NumberOfFolders == ItemSelection.Num()); } bool FActorBrowsingMode::CanPaste() const { return CanPasteFoldersOnlyFromClipboard(); } bool FActorBrowsingMode::CanPasteFoldersOnlyFromClipboard() const { // Intentionally not checking if the level is locked/hidden here, as it's better feedback for the user if they attempt to paste // and get the message explaining why it's failed, than just not having the option available to them. FString PasteString; FPlatformApplicationMisc::ClipboardPaste(PasteString); return PasteString.StartsWith("BEGIN FOLDERLIST"); } bool FActorBrowsingMode::GetFolderNamesFromPayload(const FSceneOutlinerDragDropPayload& InPayload, TArray& OutFolders, FFolder::FRootObject& OutCommonRootObject) const { return FFolder::GetFolderPathsAndCommonRootObject(InPayload.GetData(SceneOutliner::FFolderPathSelector()), OutFolders, OutCommonRootObject); } TSharedPtr FActorBrowsingMode::CreateDragDropOperation(const FPointerEvent& MouseEvent, const TArray& InTreeItems) const { FSceneOutlinerDragDropPayload DraggedObjects(InTreeItems); // If the drag contains only actors, we shortcut and create a simple FActorDragDropGraphEdOp rather than an FSceneOutlinerDragDrop composite op. if (DraggedObjects.Has() && !DraggedObjects.Has()) { return FActorDragDropGraphEdOp::New(DraggedObjects.GetData>(SceneOutliner::FWeakActorSelector())); } TSharedPtr OutlinerOp = MakeShareable(new FSceneOutlinerDragDropOp()); if (DraggedObjects.Has()) { TSharedPtr ActorOperation = MakeShareable(new FActorDragDropGraphEdOp); ActorOperation->Init(DraggedObjects.GetData>(SceneOutliner::FWeakActorSelector())); OutlinerOp->AddSubOp(ActorOperation); } if (DraggedObjects.Has()) { FFolder::FRootObject CommonRootObject; TArray DraggedFolders; if (GetFolderNamesFromPayload(DraggedObjects, DraggedFolders, CommonRootObject)) { TSharedPtr FolderOperation = MakeShareable(new FFolderDragDropOp); FolderOperation->Init(DraggedFolders, RepresentingWorld.Get(), CommonRootObject); OutlinerOp->AddSubOp(FolderOperation); } } OutlinerOp->Construct(); return OutlinerOp; } bool FActorBrowsingMode::ParseDragDrop(FSceneOutlinerDragDropPayload& OutPayload, const FDragDropOperation& Operation) const { if (Operation.IsOfType()) { const auto& OutlinerOp = static_cast(Operation); if (const auto& FolderOp = OutlinerOp.GetSubOp()) { for (const auto& Folder : FolderOp->Folders) { OutPayload.DraggedItems.Add(SceneOutliner->GetTreeItem(FFolder(FolderOp->RootObject, Folder))); } } if (const auto& ActorOp = OutlinerOp.GetSubOp()) { for (const auto& Actor : ActorOp->Actors) { OutPayload.DraggedItems.Add(SceneOutliner->GetTreeItem(Actor.Get())); } } return true; } else if (Operation.IsOfType()) { for (const TWeakObjectPtr& Actor : static_cast(Operation).Actors) { OutPayload.DraggedItems.Add(SceneOutliner->GetTreeItem(Actor.Get())); } return true; } return false; } FFolder FActorBrowsingMode::GetWorldDefaultRootFolder() const { return FFolder::GetWorldRootFolder(RepresentingWorld.Get()); } FSceneOutlinerDragValidationInfo FActorBrowsingMode::ValidateDrop(const ISceneOutlinerTreeItem& DropTarget, const FSceneOutlinerDragDropPayload& Payload) const { if (Payload.Has()) { FFolder::FRootObject TargetRootObject = DropTarget.GetRootObject(); FFolder::FRootObject CommonPayloadFoldersRootObject; TArray PayloadFolders; const bool bHasCommonRootObject = GetFolderNamesFromPayload(Payload, PayloadFolders, CommonPayloadFoldersRootObject); if (!bHasCommonRootObject) { return FSceneOutlinerDragValidationInfo(ESceneOutlinerDropCompatibility::IncompatibleGeneric, LOCTEXT("CantMoveFoldersWithMultipleRoots", "Cannot move folders with multiple roots")); } else if (CommonPayloadFoldersRootObject != TargetRootObject) { return FSceneOutlinerDragValidationInfo(ESceneOutlinerDropCompatibility::IncompatibleGeneric, LOCTEXT("CantChangeFoldersRoot", "Cannot change folders root")); } } if (const FActorTreeItem* ActorItem = DropTarget.CastTo()) { const AActor* ActorTarget = ActorItem->Actor.Get(); if (!ActorTarget) { return FSceneOutlinerDragValidationInfo(ESceneOutlinerDropCompatibility::IncompatibleGeneric, FText()); } const ILevelInstanceInterface* LevelInstanceTarget = Cast(ActorTarget); const ULevelInstanceSubsystem* LevelInstanceSubsystem = RepresentingWorld->GetSubsystem(); if (LevelInstanceTarget) { check(LevelInstanceSubsystem); if (!LevelInstanceTarget->IsEditing()) { return FSceneOutlinerDragValidationInfo(ESceneOutlinerDropCompatibility::IncompatibleGeneric, LOCTEXT("Error_AttachToClosedLevelInstance", "Cannot attach to LevelInstance which is not being edited")); } } else { if (Payload.Has()) { return FSceneOutlinerDragValidationInfo(ESceneOutlinerDropCompatibility::IncompatibleGeneric, LOCTEXT("FoldersOnActorError", "Cannot attach folders to actors")); } if (!Payload.Has()) { return FSceneOutlinerDragValidationInfo(ESceneOutlinerDropCompatibility::IncompatibleGeneric, FText()); } } FText AttachErrorMsg; bool bCanAttach = true; bool bDraggedOntoAttachmentParent = true; const auto& DragActors = Payload.GetData>(SceneOutliner::FWeakActorSelector()); for (const auto& DragActorPtr : DragActors) { AActor* DragActor = DragActorPtr.Get(); if (DragActor) { if (bCanAttach) { if (LevelInstanceSubsystem) { // Either all actors must be in a LevelInstance or none of them if (const ILevelInstanceInterface* ParentLevelInstance = LevelInstanceSubsystem->GetParentLevelInstance(DragActor)) { if (!ParentLevelInstance->IsEditing()) { return FSceneOutlinerDragValidationInfo(ESceneOutlinerDropCompatibility::IncompatibleGeneric, LOCTEXT("Error_RemoveEditingLevelInstance", "Cannot detach from a LevelInstance which is not being edited")); } } if (!LevelInstanceSubsystem->CanMoveActorToLevel(DragActor, &AttachErrorMsg)) { bCanAttach = bDraggedOntoAttachmentParent = false; break; } } if (DragActor->IsChildActor()) { AttachErrorMsg = FText::Format(LOCTEXT("Error_AttachChildActor", "Cannot move {0} as it is a child actor."), FText::FromString(DragActor->GetActorLabel())); bCanAttach = bDraggedOntoAttachmentParent = false; break; } if (!LevelInstanceTarget && !GEditor->CanParentActors(ActorTarget, DragActor, &AttachErrorMsg)) { bCanAttach = false; } } if (DragActor->GetSceneOutlinerParent() != ActorTarget) { bDraggedOntoAttachmentParent = false; } } } const FText ActorLabel = FText::FromString(ActorTarget->GetActorLabel()); if (bDraggedOntoAttachmentParent) { if (DragActors.Num() == 1) { return FSceneOutlinerDragValidationInfo(ESceneOutlinerDropCompatibility::CompatibleDetach, ActorLabel); } else { return FSceneOutlinerDragValidationInfo(ESceneOutlinerDropCompatibility::CompatibleMultipleDetach, ActorLabel); } } else if (bCanAttach) { if (DragActors.Num() == 1) { return FSceneOutlinerDragValidationInfo(ESceneOutlinerDropCompatibility::CompatibleAttach, ActorLabel); } else { return FSceneOutlinerDragValidationInfo(ESceneOutlinerDropCompatibility::CompatibleMultipleAttach, ActorLabel); } } else { if (DragActors.Num() == 1) { return FSceneOutlinerDragValidationInfo(ESceneOutlinerDropCompatibility::IncompatibleGeneric, AttachErrorMsg); } else { const FText ReasonText = FText::Format(LOCTEXT("DropOntoText", "{0}. {1}"), ActorLabel, AttachErrorMsg); return FSceneOutlinerDragValidationInfo(ESceneOutlinerDropCompatibility::IncompatibleMultipleAttach, ReasonText); } } } else if (DropTarget.IsA() || DropTarget.IsA() || DropTarget.IsA()) { const FFolderTreeItem* FolderItem = DropTarget.CastTo(); const FWorldTreeItem* WorldItem = DropTarget.CastTo(); const FLevelTreeItem* LevelItem = DropTarget.CastTo(); // WorldTreeItem and LevelTreeItem are treated as root folders (path = none), with the difference that LevelTreeItem has a RootObject. const FFolder DestinationPath = FolderItem ? FolderItem->GetFolder() : (LevelItem ? FFolder(FFolder::GetOptionalFolderRootObject(LevelItem->Level.Get()).Get(FFolder::GetInvalidRootObject())) : GetWorldDefaultRootFolder()); const FFolder::FRootObject& DestinationRootObject = DestinationPath.GetRootObject(); ILevelInstanceInterface* LevelInstanceTarget = Cast(DestinationPath.GetRootObjectPtr()); if (LevelInstanceTarget && !LevelInstanceTarget->IsEditing()) { return FSceneOutlinerDragValidationInfo(ESceneOutlinerDropCompatibility::IncompatibleGeneric, LOCTEXT("Error_DragInNonEditingLevelInstance", "Cannot drag into a LevelInstance which is not being edited")); } if (Payload.Has()) { FFolder::FRootObject CommonFolderRootObject; TArray DraggedFolders; if (GetFolderNamesFromPayload(Payload, DraggedFolders, CommonFolderRootObject)) { // Iterate over all the folders that have been dragged for (const FName& DraggedFolder : DraggedFolders) { const FName Leaf = FEditorFolderUtils::GetLeafName(DraggedFolder); const FName Parent = FEditorFolderUtils::GetParentPath(DraggedFolder); if ((CommonFolderRootObject != DestinationRootObject) && FFolder::IsRootObjectValid(CommonFolderRootObject) && FFolder::IsRootObjectValid(DestinationRootObject)) { FFormatNamedArguments Args; Args.Add(TEXT("SourceName"), FText::FromName(Leaf)); FText Text = FText::Format(LOCTEXT("CantChangeFolderRoot", "Cannot change {SourceName} folder root"), Args); return FSceneOutlinerDragValidationInfo(ESceneOutlinerDropCompatibility::IncompatibleGeneric, Text); } if (Parent == DestinationPath.GetPath()) { FFormatNamedArguments Args; Args.Add(TEXT("SourceName"), FText::FromName(Leaf)); FText Text; if (DestinationPath.IsNone()) { Text = FText::Format(LOCTEXT("FolderAlreadyAssignedRoot", "{SourceName} is already assigned to root"), Args); } else { Args.Add(TEXT("DestPath"), FText::FromName(DestinationPath.GetPath())); Text = FText::Format(LOCTEXT("FolderAlreadyAssigned", "{SourceName} is already assigned to {DestPath}"), Args); } return FSceneOutlinerDragValidationInfo(ESceneOutlinerDropCompatibility::IncompatibleGeneric, Text); } const FString DragFolderPath = DraggedFolder.ToString(); const FString LeafName = Leaf.ToString(); const FString DstFolderPath = DestinationPath.IsNone() ? FString() : DestinationPath.ToString(); const FString NewPath = DstFolderPath / LeafName; if (FActorFolders::Get().ContainsFolder(*RepresentingWorld, FFolder(DestinationRootObject, FName(*NewPath)))) { // The folder already exists FFormatNamedArguments Args; Args.Add(TEXT("DragName"), FText::FromString(LeafName)); return FSceneOutlinerDragValidationInfo(ESceneOutlinerDropCompatibility::IncompatibleGeneric, FText::Format(LOCTEXT("FolderAlreadyExistsRoot", "A folder called \"{DragName}\" already exists at this level"), Args)); } else if (DragFolderPath == DstFolderPath || DstFolderPath.StartsWith(DragFolderPath + "/")) { // Cannot drag as a child of itself FFormatNamedArguments Args; Args.Add(TEXT("FolderPath"), FText::FromName(DraggedFolder)); return FSceneOutlinerDragValidationInfo( ESceneOutlinerDropCompatibility::IncompatibleGeneric, FText::Format(LOCTEXT("ChildOfItself", "Cannot move \"{FolderPath}\" to be a child of itself"), Args)); } } } } if (Payload.Has()) { const ULevelInstanceSubsystem* LevelInstanceSubsystem = RepresentingWorld->GetSubsystem(); // Iterate over all the actors that have been dragged for (const TWeakObjectPtr& WeakActor : Payload.GetData>(SceneOutliner::FWeakActorSelector())) { const AActor* Actor = WeakActor.Get(); bool bActorContainedInLevelInstance = false; if (LevelInstanceSubsystem) { if (const ILevelInstanceInterface* ParentLevelInstance = LevelInstanceSubsystem->GetParentLevelInstance(Actor)) { if (!ParentLevelInstance->IsEditing()) { return FSceneOutlinerDragValidationInfo(ESceneOutlinerDropCompatibility::IncompatibleGeneric, LOCTEXT("Error_RemoveEditingLevelInstance", "Cannot detach from a LevelInstance which is not being edited")); } bActorContainedInLevelInstance = true; } if (const ILevelInstanceInterface* LevelInstance = Cast(Actor)) { FText Reason; if (!LevelInstanceSubsystem->CanMoveActorToLevel(Actor, &Reason)) { return FSceneOutlinerDragValidationInfo(ESceneOutlinerDropCompatibility::IncompatibleGeneric, Reason); } } } if (Actor->IsChildActor()) { return FSceneOutlinerDragValidationInfo(ESceneOutlinerDropCompatibility::IncompatibleGeneric, FText::Format(LOCTEXT("Error_AttachChildActor", "Cannot move {0} as it is a child actor."), FText::FromString(Actor->GetActorLabel()))); } else if ((Actor->GetFolderRootObject() != DestinationRootObject) && FFolder::IsRootObjectValid(Actor->GetFolderRootObject()) && FFolder::IsRootObjectValid(DestinationRootObject)) { FFormatNamedArguments Args; Args.Add(TEXT("SourceName"), FText::FromString(Actor->GetActorLabel())); FText Text = FText::Format(LOCTEXT("CantChangeActorRoot", "Cannot change {SourceName} folder root"), Args); return FSceneOutlinerDragValidationInfo(ESceneOutlinerDropCompatibility::IncompatibleGeneric, Text); } else if (Actor->GetFolder() == DestinationPath && !Actor->GetSceneOutlinerParent() && !bActorContainedInLevelInstance) { FFormatNamedArguments Args; Args.Add(TEXT("SourceName"), FText::FromString(Actor->GetActorLabel())); FText Text; if (DestinationPath.IsNone()) { Text = FText::Format(LOCTEXT("FolderAlreadyAssignedRoot", "{SourceName} is already assigned to root"), Args); } else { Args.Add(TEXT("DestPath"), FText::FromName(DestinationPath.GetPath())); Text = FText::Format(LOCTEXT("FolderAlreadyAssigned", "{SourceName} is already assigned to {DestPath}"), Args); } return FSceneOutlinerDragValidationInfo(ESceneOutlinerDropCompatibility::IncompatibleGeneric, Text); } } } // Everything else is a valid operation if (DestinationPath.IsNone()) { return FSceneOutlinerDragValidationInfo(ESceneOutlinerDropCompatibility::CompatibleGeneric, LOCTEXT("MoveToRoot", "Move to root")); } else { FFormatNamedArguments Args; Args.Add(TEXT("DestPath"), FText::FromName(DestinationPath.GetPath())); return FSceneOutlinerDragValidationInfo(ESceneOutlinerDropCompatibility::CompatibleGeneric, FText::Format(LOCTEXT("MoveInto", "Move into \"{DestPath}\""), Args)); } } else if (DropTarget.IsA()) { // we don't allow drag and drop on components for now return FSceneOutlinerDragValidationInfo(ESceneOutlinerDropCompatibility::IncompatibleGeneric, FText()); } return FSceneOutlinerDragValidationInfo::Invalid(); } void FActorBrowsingMode::OnDrop(ISceneOutlinerTreeItem& DropTarget, const FSceneOutlinerDragDropPayload& Payload, const FSceneOutlinerDragValidationInfo& ValidationInfo) const { if (const FActorTreeItem* ActorItem = DropTarget.CastTo()) { AActor* DropActor = ActorItem->Actor.Get(); if (!DropActor) { return; } FMessageLog EditorErrors("EditorErrors"); EditorErrors.NewPage(LOCTEXT("ActorAttachmentsPageLabel", "Actor attachment")); if (ValidationInfo.CompatibilityType == ESceneOutlinerDropCompatibility::CompatibleMultipleDetach || ValidationInfo.CompatibilityType == ESceneOutlinerDropCompatibility::CompatibleDetach) { const FScopedTransaction Transaction(LOCTEXT("UndoAction_DetachActors", "Detach actors")); TArray> DraggedActors = Payload.GetData>(SceneOutliner::FWeakActorSelector()); for (const auto& WeakActor : DraggedActors) { if (auto* DragActor = WeakActor.Get()) { // Detach from parent USceneComponent* RootComp = DragActor->GetRootComponent(); if (RootComp && RootComp->GetAttachParent()) { AActor* OldParent = RootComp->GetAttachParent()->GetOwner(); // Attachment is persisted on the child so modify both actors for Undo/Redo but do not mark the Parent package dirty OldParent->Modify(/*bAlwaysMarkDirty=*/false); RootComp->DetachFromComponent(FDetachmentTransformRules::KeepWorldTransform); DragActor->SetFolderPath_Recursively(OldParent->GetFolderPath()); } } } } else if (ValidationInfo.CompatibilityType == ESceneOutlinerDropCompatibility::CompatibleMultipleAttach || ValidationInfo.CompatibilityType == ESceneOutlinerDropCompatibility::CompatibleAttach) { // Show socket chooser if we have sockets to select if (ILevelInstanceInterface* TargetLevelInstance = Cast(DropActor)) { check(TargetLevelInstance->IsEditing()); const FScopedTransaction Transaction(LOCTEXT("UndoAction_MoveActorsToLevelInstance", "Move actors to LevelInstance")); const FFolder DestinationPath = FFolder(FFolder::FRootObject(DropActor)); auto MoveToDestination = [&DestinationPath](FFolderTreeItem& Item) { Item.MoveTo(DestinationPath); }; Payload.ForEachItem(MoveToDestination); // Since target root is directly the Level Instance, clear folder path TArray DraggedActors = Payload.GetData(SceneOutliner::FActorSelector()); for (auto& Actor : DraggedActors) { Actor->SetFolderPath_Recursively(FName()); } ULevelInstanceSubsystem* LevelInstanceSubsystem = RepresentingWorld->GetSubsystem(); check(LevelInstanceSubsystem); LevelInstanceSubsystem->MoveActorsTo(TargetLevelInstance, DraggedActors); } else { auto PerformAttachment = [](FName SocketName, TWeakObjectPtr Parent, const TArray> NewAttachments) { AActor* ParentActor = Parent.Get(); if (ParentActor) { // modify parent and child const FScopedTransaction Transaction(LOCTEXT("UndoAction_PerformAttachment", "Attach actors")); // Attach each child bool bAttached = false; for (auto& Child : NewAttachments) { AActor* ChildActor = Child.Get(); if (GEditor->CanParentActors(ParentActor, ChildActor)) { GEditor->ParentActors(ParentActor, ChildActor, SocketName); ChildActor->SetFolderPath_Recursively(ParentActor->GetFolderPath()); } } } }; TArray> DraggedActors = Payload.GetData>(SceneOutliner::FWeakActorSelector()); //@TODO: Should create a menu for each component that contains sockets, or have some form of disambiguation within the menu (like a fully qualified path) // Instead, we currently only display the sockets on the root component USceneComponent* Component = DropActor->GetRootComponent(); if ((Component != NULL) && (Component->HasAnySockets())) { // Create the popup FSlateApplication::Get().PushMenu( SceneOutliner->AsShared(), FWidgetPath(), SNew(SSocketChooserPopup) .SceneComponent(Component) .OnSocketChosen_Lambda(PerformAttachment, DropActor, MoveTemp(DraggedActors)), FSlateApplication::Get().GetCursorPos(), FPopupTransitionEffect(FPopupTransitionEffect::TypeInPopup) ); } else { PerformAttachment(NAME_None, DropActor, MoveTemp(DraggedActors)); } } } // Report errors EditorErrors.Notify(NSLOCTEXT("ActorAttachmentError", "AttachmentsFailed", "Attachments Failed!")); } else if (DropTarget.IsA() || DropTarget.IsA() || DropTarget.IsA()) { const FFolderTreeItem* FolderItem = DropTarget.CastTo(); const FWorldTreeItem* WorldItem = DropTarget.CastTo(); const FLevelTreeItem* LevelItem = DropTarget.CastTo(); // WorldTreeItem and LevelTreeItem are treated as root folders (path = none), with the difference that LevelTreeItem has a RootObject. const FFolder DestinationPath = FolderItem ? FolderItem->GetFolder() : (LevelItem ? FFolder(FFolder::GetOptionalFolderRootObject(LevelItem->Level.Get()).Get(FFolder::GetInvalidRootObject())) : GetWorldDefaultRootFolder()); const FScopedTransaction Transaction(LOCTEXT("MoveOutlinerItems", "Move World Outliner Items")); auto MoveToDestination = [&DestinationPath](FFolderTreeItem& Item) { Item.MoveTo(DestinationPath); }; Payload.ForEachItem(MoveToDestination); // Set the folder path on all the dragged actors, and detach any that need to be moved if (Payload.Has()) { TSet ParentActors; TSet ChildActors; TArray MovingActorsToValidRootObject; Payload.ForEachItem([&DestinationPath, &ParentActors, &ChildActors, &MovingActorsToValidRootObject](const FActorTreeItem& ActorItem) { AActor* Actor = ActorItem.Actor.Get(); if (Actor) { // First mark this object as a parent, then set its children's path ParentActors.Add(Actor); const FFolder SrcFolder = Actor->GetFolder(); // If the folder root object changes, 1st pass will put actors at root. 2nd pass will set the destination path. FName NewPath = (SrcFolder.GetRootObject() == DestinationPath.GetRootObject()) ? DestinationPath.GetPath() : NAME_None; Actor->SetFolderPath(NewPath); FActorEditorUtils::TraverseActorTree_ParentFirst(Actor, [&](AActor* InActor) { ChildActors.Add(InActor); InActor->SetFolderPath(NewPath); return true; }, false); if ((Actor->GetFolderRootObject() != DestinationPath.GetRootObject()) && SrcFolder.IsRootObjectPersistentLevel() && (DestinationPath.IsRootObjectValid() && !DestinationPath.IsRootObjectPersistentLevel())) { MovingActorsToValidRootObject.Add(Actor); } } }); // Detach parent actors for (const AActor* Parent : ParentActors) { auto* RootComp = Parent->GetRootComponent(); // We don't detach if it's a child of another that's been dragged if (RootComp && RootComp->GetAttachParent() && !ChildActors.Contains(Parent)) { if (AActor* OldParentActor = RootComp->GetAttachParent()->GetOwner()) { // Attachment is persisted on the child so modify both actors for Undo/Redo but do not mark the Parent package dirty OldParentActor->Modify(/*bAlwaysMarkDirty=*/false); } RootComp->DetachFromComponent(FDetachmentTransformRules::KeepWorldTransform); } } auto MoveActorsToLevel = [](const TArray& InActorsToMove, ULevel* InDestLevel, const FName& InDestinationPath) { // We are moving actors to another level const bool bWarnAboutReferences = true; const bool bWarnAboutRenaming = true; const bool bMoveAllOrFail = true; TArray MovedActors; if (!EditorLevelUtils::MoveActorsToLevel(InActorsToMove, InDestLevel, bWarnAboutReferences, bWarnAboutRenaming, bMoveAllOrFail, &MovedActors)) { UE_LOG(LogActorBrowser, Warning, TEXT("Failed to move actors because not all actors could be moved")); } // Once moved, update actors folder path for (AActor* Actor : MovedActors) { Actor->SetFolderPath_Recursively(InDestinationPath); } }; if (DestinationPath.IsRootObjectPersistentLevel()) { const ULevelInstanceSubsystem* LevelInstanceSubsystem = RepresentingWorld->GetSubsystem(); check(LevelInstanceSubsystem); ULevel* DestinationLevel = RepresentingWorld->PersistentLevel; check(DestinationLevel); TArray LevelInstanceActorsToMove; TArray ActorsToMoveToPersistentLevel; Payload.ForEachItem([LevelInstanceSubsystem, &LevelInstanceActorsToMove, &ActorsToMoveToPersistentLevel](const FActorTreeItem& ActorItem) { AActor* Actor = ActorItem.Actor.Get(); if (const ILevelInstanceInterface* ParentLevelInstance = LevelInstanceSubsystem->GetParentLevelInstance(Actor)) { check(ParentLevelInstance->IsEditing()); LevelInstanceActorsToMove.Add(Actor); } else { const FFolder ActorSrcFolder = Actor->GetFolder(); if (ActorSrcFolder.IsRootObjectValid() && !ActorSrcFolder.IsRootObjectPersistentLevel()) { ActorsToMoveToPersistentLevel.Add(Actor); } } }); // We are moving actors outside of an editing level instance to a folder (or root) into the persistent level. if (LevelInstanceActorsToMove.Num() > 0) { TArray MovedActors; LevelInstanceSubsystem->MoveActorsToLevel(LevelInstanceActorsToMove, DestinationLevel, &MovedActors); // Once moved, update actors folder path for (AActor* Actor : MovedActors) { Actor->SetFolderPath_Recursively(DestinationPath.GetPath()); } } if (ActorsToMoveToPersistentLevel.Num() > 0) { MoveActorsToLevel(ActorsToMoveToPersistentLevel, DestinationLevel, DestinationPath.GetPath()); } } else if (MovingActorsToValidRootObject.Num()) { if (ILevelInstanceInterface* TargetLevelInstance = Cast(DestinationPath.GetRootObjectPtr())) { // We are moving actors inside an editing level instance check(TargetLevelInstance->IsEditing()); ULevelInstanceSubsystem* LevelInstanceSubsystem = RepresentingWorld->GetSubsystem(); check(LevelInstanceSubsystem); TArray MovedActors; LevelInstanceSubsystem->MoveActorsTo(TargetLevelInstance, MovingActorsToValidRootObject, &MovedActors); // Once moved, update actors folder path for (AActor* Actor : MovedActors) { Actor->SetFolderPath_Recursively(DestinationPath.GetPath()); } } else if (ULevel* DestinationLevel = Cast(DestinationPath.GetRootObjectPtr())) { MoveActorsToLevel(MovingActorsToValidRootObject, DestinationLevel, DestinationPath.GetPath()); } } } } } FFolder FActorBrowsingMode::CreateNewFolder() { const FScopedTransaction Transaction(LOCTEXT("UndoAction_CreateFolder", "Create Folder")); TArray SelectedFolders = SceneOutliner->GetSelection().GetData(SceneOutliner::FFolderPathSelector()); const FFolder NewFolderName = FActorFolders::Get().GetDefaultFolderForSelection(*RepresentingWorld, &SelectedFolders); FActorFolders::Get().CreateFolderContainingSelection(*RepresentingWorld, NewFolderName); return NewFolderName; } FFolder FActorBrowsingMode::GetFolder(const FFolder& ParentPath, const FName& LeafName) { // Return a unique folder under the provided parent path & root object and using the provided leaf name return FActorFolders::Get().GetFolderName(*RepresentingWorld, ParentPath, LeafName); } bool FActorBrowsingMode::CreateFolder(const FFolder& NewPath) { return FActorFolders::Get().CreateFolder(*RepresentingWorld, NewPath); } bool FActorBrowsingMode::ReparentItemToFolder(const FFolder& FolderPath, const FSceneOutlinerTreeItemPtr& Item) { if (FActorTreeItem* ActorItem = Item->CastTo()) { // Make sure actor has the same root object before updating path if (ActorItem->Actor->GetFolderRootObject() == FolderPath.GetRootObject()) { ActorItem->Actor->SetFolderPath_Recursively(FolderPath.GetPath()); return true; } } return false; } namespace ActorBrowsingModeUtils { static void RecursiveFolderExpandChildren(SSceneOutliner* SceneOutliner, const FSceneOutlinerTreeItemPtr& Item) { if (Item.IsValid()) { for (const TWeakPtr& Child : Item->GetChildren()) { FSceneOutlinerTreeItemPtr ChildPtr = Child.Pin(); SceneOutliner->SetItemExpansion(ChildPtr, true); RecursiveFolderExpandChildren(SceneOutliner, ChildPtr); } } } static void RecursiveActorSelect(SSceneOutliner* SceneOutliner, const FSceneOutlinerTreeItemPtr& Item, bool bSelectImmediateChildrenOnly) { if (Item.IsValid()) { // If the current item is an actor, ensure to select it as well if (FActorTreeItem* ActorItem = Item->CastTo()) { if (AActor* Actor = ActorItem->Actor.Get()) { GEditor->SelectActor(Actor, true, false); } } // Select all children for (const TWeakPtr& Child : Item->GetChildren()) { FSceneOutlinerTreeItemPtr ChildPtr = Child.Pin(); if (ChildPtr.IsValid()) { if (FActorTreeItem* ActorItem = ChildPtr->CastTo()) { if (AActor* Actor = ActorItem->Actor.Get()) { GEditor->SelectActor(Actor, true, false); } } else if (FFolderTreeItem* FolderItem = ChildPtr->CastTo()) { SceneOutliner->SetItemSelection(FolderItem->AsShared(), true); } if (!bSelectImmediateChildrenOnly) { for (const TWeakPtr& Grandchild : ChildPtr->GetChildren()) { RecursiveActorSelect(SceneOutliner, Grandchild.Pin(), bSelectImmediateChildrenOnly); } } } } } } static void RecursiveAddItemsToActorGuidList(const TArray& Items, TArray& List) { for (const FSceneOutlinerTreeItemPtr& Item : Items) { if (const FActorDescTreeItem* const ActorDescTreeItem = Item->CastTo()) { List.Add(ActorDescTreeItem->GetGuid()); } else if (const FActorTreeItem* const ActorTreeItem = Item->CastTo()) { if (ActorTreeItem->Actor.IsValid()) { List.Add(ActorTreeItem->Actor->GetActorGuid()); } } TArray ChildrenItems; for (const auto& Child : Item->GetChildren()) { if (Child.IsValid()) { ChildrenItems.Add(Child.Pin()); } } if (ChildrenItems.Num()) { RecursiveAddItemsToActorGuidList(ChildrenItems, List); } } }; } void FActorBrowsingMode::SelectFoldersDescendants(const TArray& FolderItems, bool bSelectImmediateChildrenOnly) { // Expand everything before beginning selection for (FFolderTreeItem* Folder : FolderItems) { FSceneOutlinerTreeItemPtr FolderPtr = Folder->AsShared(); SceneOutliner->SetItemExpansion(FolderPtr, true); if (!bSelectImmediateChildrenOnly) { ActorBrowsingModeUtils::RecursiveFolderExpandChildren(SceneOutliner, FolderPtr); } } // batch selection GEditor->GetSelectedActors()->BeginBatchSelectOperation(); for (FFolderTreeItem* Folder : FolderItems) { ActorBrowsingModeUtils::RecursiveActorSelect(SceneOutliner, Folder->AsShared(), bSelectImmediateChildrenOnly); } GEditor->GetSelectedActors()->EndBatchSelectOperation(/*bNotify*/false); GEditor->NoteSelectionChange(); } void FActorBrowsingMode::PinItems(const TArray& InItems) { UWorldPartition* const WorldPartition = RepresentingWorld->GetWorldPartition(); if (!WorldPartition) { return; } TArray ActorsToPin; ActorBrowsingModeUtils::RecursiveAddItemsToActorGuidList(InItems, ActorsToPin); if (ActorsToPin.Num()) { GEditor->GetSelectedActors()->BeginBatchSelectOperation(); GEditor->SelectNone(/*bNoteSelectionChange=*/false, /*bDeselectBSPSurfs=*/true); WorldPartition->PinActors(ActorsToPin); AActor* LastPinnedActor = nullptr; for (const FGuid& ActorGuid : ActorsToPin) { if (FWorldPartitionHandle ActorHandle(WorldPartition, ActorGuid); ActorHandle.IsValid()) { if (AActor* PinnedActor = ActorHandle->GetActor()) { GEditor->SelectActor(PinnedActor, /*bInSelected=*/true, /*bNotify=*/false); LastPinnedActor = PinnedActor; } } } GEditor->GetSelectedActors()->EndBatchSelectOperation(/*bNotify=*/true); if (LastPinnedActor) { SceneOutliner->OnItemAdded(LastPinnedActor, SceneOutliner::ENewItemAction::ScrollIntoView); } } } void FActorBrowsingMode::UnpinItems(const TArray& InItems) { UWorldPartition* const WorldPartition = RepresentingWorld->GetWorldPartition(); if (!WorldPartition) { return; } TArray ActorsToUnpin; ActorBrowsingModeUtils::RecursiveAddItemsToActorGuidList(InItems, ActorsToUnpin); if (ActorsToUnpin.Num()) { GEditor->GetSelectedActors()->BeginBatchSelectOperation(); AActor* LastPinnedActor = nullptr; for (const FGuid& ActorGuid : ActorsToUnpin) { if (FWorldPartitionHandle ActorHandle(WorldPartition, ActorGuid); ActorHandle.IsValid()) { if (AActor* PinnedActor = ActorHandle->GetActor()) { GEditor->SelectActor(PinnedActor, /*bInSelected=*/false, /*bNotify=*/false); } } } WorldPartition->UnpinActors(ActorsToUnpin); GEditor->GetSelectedActors()->EndBatchSelectOperation(/*bNotify=*/true); } } void FActorBrowsingMode::PinSelectedItems() { const FSceneOutlinerItemSelection Selection = SceneOutliner->GetSelection(); if (Selection.Num()) { TArray ItemsToPin; Selection.ForEachItem([this, &ItemsToPin](const FSceneOutlinerTreeItemPtr& TreeItem) { ItemsToPin.Add(TreeItem); return true; }); PinItems(ItemsToPin); } } void FActorBrowsingMode::UnpinSelectedItems() { const FSceneOutlinerItemSelection Selection = SceneOutliner->GetSelection(); if(Selection.Num()) { TArray ItemsToUnpin; Selection.ForEachItem([this, &ItemsToUnpin](const FSceneOutlinerTreeItemPtr& TreeItem) { ItemsToUnpin.Add(TreeItem); return true; }); UnpinItems(ItemsToUnpin); } } FCreateSceneOutlinerMode FActorBrowsingMode::CreateFolderPickerMode(const FFolder::FRootObject& InRootObject) const { auto MoveSelectionTo = [this, InRootObject](const FSceneOutlinerTreeItemRef& NewParent) { if (FWorldTreeItem* WorldItem = NewParent->CastTo()) { SceneOutliner->MoveSelectionTo(GetWorldDefaultRootFolder()); } else if (FFolderTreeItem* FolderItem = NewParent->CastTo()) { SceneOutliner->MoveSelectionTo(FolderItem->GetFolder()); } else if (FActorTreeItem* ActorItem = NewParent->CastTo()) { if (FFolder::IsRootObjectValid(InRootObject)) { SceneOutliner->MoveSelectionTo(FFolder(InRootObject)); } } else if (FLevelTreeItem* LevelItem = NewParent->CastTo()) { if (FFolder::IsRootObjectValid(InRootObject)) { SceneOutliner->MoveSelectionTo(FFolder(InRootObject)); } } }; return FCreateSceneOutlinerMode::CreateLambda([this, MoveSelectionTo, InRootObject](SSceneOutliner* Outliner) { return new FActorFolderPickingMode(Outliner, FOnSceneOutlinerItemPicked::CreateLambda(MoveSelectionTo), nullptr, InRootObject); }); } void FActorBrowsingMode::OnDuplicateSelected() { GUnrealEd->Exec(RepresentingWorld.Get(), TEXT("DUPLICATE")); } void FActorBrowsingMode::OnEditCutActorsBegin() { // Only a callback in actor browsing mode SceneOutliner->CopyFoldersBegin(); SceneOutliner->DeleteFoldersBegin(); } void FActorBrowsingMode::OnEditCutActorsEnd() { // Only a callback in actor browsing mode SceneOutliner->CopyFoldersEnd(); SceneOutliner->DeleteFoldersEnd(); } void FActorBrowsingMode::OnEditCopyActorsBegin() { // Only a callback in actor browsing mode SceneOutliner->CopyFoldersBegin(); } void FActorBrowsingMode::OnEditCopyActorsEnd() { // Only a callback in actor browsing mode SceneOutliner->CopyFoldersEnd(); } void FActorBrowsingMode::OnEditPasteActorsBegin() { // Only a callback in actor browsing mode const TArray FolderPaths = SceneOutliner->GetClipboardPasteFolders(); SceneOutliner->PasteFoldersBegin(FolderPaths); } void FActorBrowsingMode::OnEditPasteActorsEnd() { // Only a callback in actor browsing mode SceneOutliner->PasteFoldersEnd(); } void FActorBrowsingMode::OnDuplicateActorsBegin() { // Only a callback in actor browsing mode FFolder::FRootObject CommonRootObject; TArray SelectedFolderPaths; FFolder::GetFolderPathsAndCommonRootObject(SceneOutliner->GetSelection().GetData(SceneOutliner::FFolderPathSelector()), SelectedFolderPaths, CommonRootObject); SceneOutliner->PasteFoldersBegin(SelectedFolderPaths); } void FActorBrowsingMode::OnDuplicateActorsEnd() { // Only a callback in actor browsing mode SceneOutliner->PasteFoldersEnd(); } void FActorBrowsingMode::OnDeleteActorsBegin() { SceneOutliner->DeleteFoldersBegin(); } void FActorBrowsingMode::OnDeleteActorsEnd() { SceneOutliner->DeleteFoldersEnd(); } struct FActorBrowsingModeConfig* FActorBrowsingMode::GetMutableConfig() { FName OutlinerIdentifier = SceneOutliner->GetOutlinerIdentifier(); if (OutlinerIdentifier.IsNone()) { return nullptr; } return &UActorBrowserConfig::Get()->ActorBrowsers.FindOrAdd(OutlinerIdentifier); } const FActorBrowsingModeConfig* FActorBrowsingMode::GetConstConfig() const { FName OutlinerIdentifier = SceneOutliner->GetOutlinerIdentifier(); if (OutlinerIdentifier.IsNone()) { return nullptr; } return UActorBrowserConfig::Get()->ActorBrowsers.Find(OutlinerIdentifier); } void FActorBrowsingMode::SaveConfig() { UActorBrowserConfig::Get()->SaveEditorConfig(); } #undef LOCTEXT_NAMESPACE