// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved. #include "ContentBrowserPCH.h" #include "PathViewTypes.h" #include "PathContextMenu.h" #include "ISourceControlModule.h" #include "SourceControlWindows.h" #include "ContentBrowserModule.h" #include "ReferenceViewer.h" #include "ISizeMapModule.h" #include "AssetToolsModule.h" #include "Editor/UnrealEd/Public/PackageTools.h" #include "SColorPicker.h" #include "GenericCommands.h" #include "NativeClassHierarchy.h" #define LOCTEXT_NAMESPACE "ContentBrowser" FPathContextMenu::FPathContextMenu(const TWeakPtr& InParentContent) : ParentContent(InParentContent) , bCanExecuteSCCCheckOut(false) , bCanExecuteSCCOpenForAdd(false) , bCanExecuteSCCCheckIn(false) { } void FPathContextMenu::SetOnNewAssetRequested(const FNewAssetOrClassContextMenu::FOnNewAssetRequested& InOnNewAssetRequested) { OnNewAssetRequested = InOnNewAssetRequested; } void FPathContextMenu::SetOnNewClassRequested(const FNewAssetOrClassContextMenu::FOnNewClassRequested& InOnNewClassRequested) { OnNewClassRequested = InOnNewClassRequested; } void FPathContextMenu::SetOnImportAssetRequested( const FNewAssetOrClassContextMenu::FOnImportAssetRequested& InOnImportAssetRequested ) { OnImportAssetRequested = InOnImportAssetRequested; } void FPathContextMenu::SetOnRenameFolderRequested(const FOnRenameFolderRequested& InOnRenameFolderRequested) { OnRenameFolderRequested = InOnRenameFolderRequested; } void FPathContextMenu::SetOnFolderDeleted(const FOnFolderDeleted& InOnFolderDeleted) { OnFolderDeleted = InOnFolderDeleted; } void FPathContextMenu::SetSelectedPaths(const TArray& InSelectedPaths) { SelectedPaths = InSelectedPaths; } TSharedRef FPathContextMenu::MakePathViewContextMenuExtender(const TArray& InSelectedPaths) { // Cache any vars that are used in determining if you can execute any actions. // Useful for actions whose "CanExecute" will not change or is expensive to calculate. CacheCanExecuteVars(); // Get all menu extenders for this context menu from the content browser module FContentBrowserModule& ContentBrowserModule = FModuleManager::GetModuleChecked( TEXT("ContentBrowser") ); TArray MenuExtenderDelegates = ContentBrowserModule.GetAllPathViewContextMenuExtenders(); TArray> Extenders; for (int32 i = 0; i < MenuExtenderDelegates.Num(); ++i) { if (MenuExtenderDelegates[i].IsBound()) { Extenders.Add(MenuExtenderDelegates[i].Execute( SelectedPaths )); } } TSharedPtr MenuExtender = FExtender::Combine(Extenders); MenuExtender->AddMenuExtension("NewFolder", EExtensionHook::After, TSharedPtr(), FMenuExtensionDelegate::CreateSP(this, &FPathContextMenu::MakePathViewContextMenu)); return MenuExtender.ToSharedRef(); } void FPathContextMenu::MakePathViewContextMenu(FMenuBuilder& MenuBuilder) { int32 NumAssetPaths, NumClassPaths; ContentBrowserUtils::CountPathTypes(SelectedPaths, NumAssetPaths, NumClassPaths); // Only add something if at least one folder is selected if ( SelectedPaths.Num() > 0 ) { const bool bHasAssetPaths = NumAssetPaths > 0; const bool bHasClassPaths = NumClassPaths > 0; // Common operations section // MenuBuilder.BeginSection("PathViewFolderOptions", LOCTEXT("PathViewOptionsMenuHeading", "Folder Options") ); { if(bHasAssetPaths) { FText NewAssetToolTip; if(SelectedPaths.Num() == 1) { if(CanCreateAsset()) { NewAssetToolTip = FText::Format(LOCTEXT("NewAssetTooltip_CreateIn", "Create a new asset in {0}."), FText::FromString(SelectedPaths[0])); } else { NewAssetToolTip = FText::Format(LOCTEXT("NewAssetTooltip_InvalidPath", "Cannot create new assets in {0}."), FText::FromString(SelectedPaths[0])); } } else { NewAssetToolTip = LOCTEXT("NewAssetTooltip_InvalidNumberOfPaths", "Can only create assets when there is a single path selected."); } // New Asset (submenu) MenuBuilder.AddSubMenu( LOCTEXT( "NewAssetLabel", "New Asset" ), NewAssetToolTip, FNewMenuDelegate::CreateRaw( this, &FPathContextMenu::MakeNewAssetSubMenu ), FUIAction( FExecuteAction(), FCanExecuteAction::CreateRaw( this, &FPathContextMenu::CanCreateAsset ) ), NAME_None, EUserInterfaceActionType::Button, false, FSlateIcon() ); } if(bHasClassPaths) { FText NewClassToolTip; if(SelectedPaths.Num() == 1) { if(CanCreateClass()) { NewClassToolTip = FText::Format(LOCTEXT("NewClassTooltip_CreateIn", "Create a new class in {0}."), FText::FromString(SelectedPaths[0])); } else { NewClassToolTip = FText::Format(LOCTEXT("NewClassTooltip_InvalidPath", "Cannot create new classes in {0}."), FText::FromString(SelectedPaths[0])); } } else { NewClassToolTip = LOCTEXT("NewClassTooltip_InvalidNumberOfPaths", "Can only create classes when there is a single path selected."); } // New Class MenuBuilder.AddMenuEntry( LOCTEXT("NewClassLabel", "New C++ Class..."), NewClassToolTip, FSlateIcon(FEditorStyle::GetStyleSetName(), "MainFrame.AddCodeToProject"), FUIAction( FExecuteAction::CreateRaw( this, &FPathContextMenu::ExecuteCreateClass ), FCanExecuteAction::CreateRaw( this, &FPathContextMenu::CanCreateClass ) ) ); } // Explore MenuBuilder.AddMenuEntry( ContentBrowserUtils::GetExploreFolderText(), LOCTEXT("ExploreTooltip", "Finds this folder on disk."), FSlateIcon(), FUIAction( FExecuteAction::CreateSP( this, &FPathContextMenu::ExecuteExplore ) ) ); MenuBuilder.AddMenuEntry(FGenericCommands::Get().Rename, NAME_None, LOCTEXT("RenameFolder", "Rename"), LOCTEXT("RenameFolderTooltip", "Rename the selected folder.") ); // If any colors have already been set, display color options as a sub menu if ( ContentBrowserUtils::HasCustomColors() ) { // Set Color (submenu) MenuBuilder.AddSubMenu( LOCTEXT("SetColor", "Set Color"), LOCTEXT("SetColorTooltip", "Sets the color this folder should appear as."), FNewMenuDelegate::CreateRaw( this, &FPathContextMenu::MakeSetColorSubMenu ), false, FSlateIcon() ); } else { // Set Color MenuBuilder.AddMenuEntry( LOCTEXT("SetColor", "Set Color"), LOCTEXT("SetColorTooltip", "Sets the color this folder should appear as."), FSlateIcon(), FUIAction( FExecuteAction::CreateSP( this, &FPathContextMenu::ExecutePickColor ) ) ); } } MenuBuilder.EndSection(); if(bHasAssetPaths) { // Bulk operations section // MenuBuilder.BeginSection("PathContextBulkOperations", LOCTEXT("AssetTreeBulkMenuHeading", "Bulk Operations") ); { // Save MenuBuilder.AddMenuEntry( LOCTEXT("SaveFolder", "Save All"), LOCTEXT("SaveFolderTooltip", "Saves all modified assets in this folder."), FSlateIcon(), FUIAction( FExecuteAction::CreateSP( this, &FPathContextMenu::ExecuteSaveFolder ) ) ); // Delete MenuBuilder.AddMenuEntry(FGenericCommands::Get().Delete, NAME_None, LOCTEXT("DeleteFolder", "Delete"), LOCTEXT("DeleteFolderTooltip", "Removes this folder and all assets it contains."), FSlateIcon() ); // Reference Viewer MenuBuilder.AddMenuEntry( LOCTEXT("ReferenceViewer", "Reference Viewer..."), LOCTEXT("ReferenceViewerOnFolderTooltip", "Shows a graph of references for this folder."), FSlateIcon(), FUIAction( FExecuteAction::CreateSP( this, &FPathContextMenu::ExecuteReferenceViewer ) ) ); // Size Map MenuBuilder.AddMenuEntry( LOCTEXT("SizeMap", "Size Map..."), LOCTEXT("SizeMapOnFolderTooltip", "Shows an interactive map of the approximate memory used by the assets in this folder and everything they reference."), FSlateIcon(), FUIAction( FExecuteAction::CreateSP( this, &FPathContextMenu::ExecuteSizeMap ) ) ); // Fix Up Redirectors in Folder MenuBuilder.AddMenuEntry( LOCTEXT("FixUpRedirectorsInFolder", "Fix Up Redirectors in Folder"), LOCTEXT("FixUpRedirectorsInFolderTooltip", "Finds referencers to all redirectors in the selected folders and resaves them if possible, then deletes any redirectors that had all their referencers fixed."), FSlateIcon(), FUIAction( FExecuteAction::CreateSP( this, &FPathContextMenu::ExecuteFixUpRedirectorsInFolder ) ) ); if ( NumAssetPaths == 1 && NumClassPaths == 0 ) { // Migrate Folder MenuBuilder.AddMenuEntry( LOCTEXT("MigrateFolder", "Migrate..."), LOCTEXT("MigrateFolderTooltip", "Copies assets found in this folder and their dependencies to another game content folder."), FSlateIcon(), FUIAction( FExecuteAction::CreateSP( this, &FPathContextMenu::ExecuteMigrateFolder ) ) ); } } MenuBuilder.EndSection(); // Source control section // MenuBuilder.BeginSection("PathContextSourceControl", LOCTEXT("AssetTreeSCCMenuHeading", "Source Control") ); ISourceControlProvider& SourceControlProvider = ISourceControlModule::Get().GetProvider(); if ( SourceControlProvider.IsEnabled() ) { // Check out MenuBuilder.AddMenuEntry( LOCTEXT("FolderSCCCheckOut", "Check Out"), LOCTEXT("FolderSCCCheckOutTooltip", "Checks out all assets from source control which are in this folder."), FSlateIcon(), FUIAction( FExecuteAction::CreateSP( this, &FPathContextMenu::ExecuteSCCCheckOut ), FCanExecuteAction::CreateSP( this, &FPathContextMenu::CanExecuteSCCCheckOut ) ) ); // Open for Add MenuBuilder.AddMenuEntry( LOCTEXT("FolderSCCOpenForAdd", "Mark For Add"), LOCTEXT("FolderSCCOpenForAddTooltip", "Adds all assets to source control that are in this folder and not already added."), FSlateIcon(), FUIAction( FExecuteAction::CreateSP( this, &FPathContextMenu::ExecuteSCCOpenForAdd ), FCanExecuteAction::CreateSP( this, &FPathContextMenu::CanExecuteSCCOpenForAdd ) ) ); // Check in MenuBuilder.AddMenuEntry( LOCTEXT("FolderSCCCheckIn", "Check In"), LOCTEXT("FolderSCCCheckInTooltip", "Checks in all assets to source control which are in this folder."), FSlateIcon(), FUIAction( FExecuteAction::CreateSP( this, &FPathContextMenu::ExecuteSCCCheckIn ), FCanExecuteAction::CreateSP( this, &FPathContextMenu::CanExecuteSCCCheckIn ) ) ); // Sync MenuBuilder.AddMenuEntry( LOCTEXT("FolderSCCSync", "Sync"), LOCTEXT("FolderSCCSyncTooltip", "Syncs all the assets in this folder to the latest version."), FSlateIcon(), FUIAction( FExecuteAction::CreateSP( this, &FPathContextMenu::ExecuteSCCSync ), FCanExecuteAction::CreateSP( this, &FPathContextMenu::CanExecuteSCCSync ) ) ); } else { MenuBuilder.AddMenuEntry( LOCTEXT("FolderSCCConnect", "Connect To Source Control"), LOCTEXT("FolderSCCConnectTooltip", "Connect to source control to allow source control operations to be performed on content and levels."), FSlateIcon(), FUIAction( FExecuteAction::CreateSP( this, &FPathContextMenu::ExecuteSCCConnect ), FCanExecuteAction::CreateSP( this, &FPathContextMenu::CanExecuteSCCConnect ) ) ); } MenuBuilder.EndSection(); } } } bool FPathContextMenu::CanCreateAsset() const { // We can only create assets when we have a single asset path selected return SelectedPaths.Num() == 1 && !ContentBrowserUtils::IsClassPath(SelectedPaths[0]); } void FPathContextMenu::MakeNewAssetSubMenu(FMenuBuilder& MenuBuilder) { if ( SelectedPaths.Num() ) { FNewAssetOrClassContextMenu::MakeContextMenu( MenuBuilder, SelectedPaths, OnNewAssetRequested, FNewAssetOrClassContextMenu::FOnNewClassRequested(), FNewAssetOrClassContextMenu::FOnNewFolderRequested(), OnImportAssetRequested, FNewAssetOrClassContextMenu::FOnGetContentRequested() ); } } void FPathContextMenu::ExecuteCreateClass() { OnNewClassRequested.ExecuteIfBound(SelectedPaths[0]); } bool FPathContextMenu::CanCreateClass() const { // We can only create assets when we have a single class path selected return SelectedPaths.Num() == 1 && ContentBrowserUtils::IsValidPathToCreateNewClass(SelectedPaths[0]); } void FPathContextMenu::MakeSetColorSubMenu(FMenuBuilder& MenuBuilder) { // New Color MenuBuilder.AddMenuEntry( LOCTEXT("NewColor", "New Color"), LOCTEXT("NewColorTooltip", "Changes the color this folder should appear as."), FSlateIcon(), FUIAction( FExecuteAction::CreateSP( this, &FPathContextMenu::ExecutePickColor ) ) ); // Clear Color (only required if any of the selection has one) if ( SelectedHasCustomColors() ) { MenuBuilder.AddMenuEntry( LOCTEXT("ClearColor", "Clear Color"), LOCTEXT("ClearColorTooltip", "Resets the color this folder appears as."), FSlateIcon(), FUIAction( FExecuteAction::CreateSP( this, &FPathContextMenu::ExecuteResetColor ) ) ); } // Add all the custom colors the user has chosen so far TArray< FLinearColor > CustomColors; if ( ContentBrowserUtils::HasCustomColors( &CustomColors ) ) { MenuBuilder.BeginSection("PathContextCustomColors", LOCTEXT("CustomColorsExistingColors", "Existing Colors") ); { for ( int32 ColorIndex = 0; ColorIndex < CustomColors.Num(); ColorIndex++ ) { const FLinearColor& Color = CustomColors[ ColorIndex ]; MenuBuilder.AddWidget( SNew(SHorizontalBox) +SHorizontalBox::Slot() .AutoWidth() .Padding(2, 0, 0, 0) [ SNew(SButton) .ButtonStyle( FEditorStyle::Get(), "Menu.Button" ) .OnClicked( this, &FPathContextMenu::OnColorClicked, Color ) [ SNew(SColorBlock) .Color( Color ) .Size( FVector2D(77,16) ) ] ], LOCTEXT("CustomColor", ""), /*bNoIndent=*/true ); } } MenuBuilder.EndSection(); } } void FPathContextMenu::ExecuteMigrateFolder() { const FString& SourcesPath = GetFirstSelectedPath(); if ( ensure(SourcesPath.Len()) ) { // @todo Make sure the asset registry has completed discovering assets, or else GetAssetsByPath() will not find all the assets in the folder! Add some UI to wait for this with a cancel button FAssetRegistryModule& AssetRegistryModule = FModuleManager::Get().LoadModuleChecked(TEXT("AssetRegistry")); if ( AssetRegistryModule.Get().IsLoadingAssets() ) { FMessageDialog::Open(EAppMsgType::Ok, LOCTEXT( "MigrateFolderAssetsNotDiscovered", "You must wait until asset discovery is complete to migrate a folder" )); return; } // Get a list of package names for input into MigratePackages TArray AssetDataList; TArray PackageNames; ContentBrowserUtils::GetAssetsInPaths(SelectedPaths, AssetDataList); for ( auto AssetIt = AssetDataList.CreateConstIterator(); AssetIt; ++AssetIt ) { PackageNames.Add((*AssetIt).PackageName); } // Load all the assets in the selected paths FAssetToolsModule& AssetToolsModule = FModuleManager::Get().LoadModuleChecked("AssetTools"); AssetToolsModule.Get().MigratePackages( PackageNames ); } } void FPathContextMenu::ExecuteExplore() { for (int32 PathIdx = 0; PathIdx < SelectedPaths.Num(); ++PathIdx) { const FString& Path = SelectedPaths[PathIdx]; FString FilePath; if (ContentBrowserUtils::IsClassPath(Path)) { TSharedRef NativeClassHierarchy = FContentBrowserSingleton::Get().GetNativeClassHierarchy(); if (NativeClassHierarchy->GetFileSystemPath(Path, FilePath)) { FilePath = IFileManager::Get().ConvertToAbsolutePathForExternalAppForRead(*FilePath); } } else { FilePath = FPaths::ConvertRelativePathToFull(FPackageName::LongPackageNameToFilename(Path + TEXT("/"))); } if (!FilePath.IsEmpty()) { // If the folder has not yet been created, make is right before we try to explore to it if (!IFileManager::Get().DirectoryExists(*FilePath)) { IFileManager::Get().MakeDirectory(*FilePath, /*Tree=*/true); } FPlatformProcess::ExploreFolder(*FilePath); } } } bool FPathContextMenu::CanExecuteRename() const { // We can't rename when we have more than one path selected if (SelectedPaths.Num() != 1) { return false; } // We can't rename a root folder if (ContentBrowserUtils::IsRootDir(SelectedPaths[0])) { return false; } // We can't rename *any* folders that belong to class roots if (ContentBrowserUtils::IsClassPath(SelectedPaths[0])) { return false; } return true; } void FPathContextMenu::ExecuteRename() { check(SelectedPaths.Num() == 1); if (OnRenameFolderRequested.IsBound()) { OnRenameFolderRequested.Execute(SelectedPaths[0]); } } void FPathContextMenu::ExecuteResetColor() { ResetColors(); } void FPathContextMenu::ExecutePickColor() { // Spawn a color picker, so the user can select which color they want TArray LinearColorArray; FColorPickerArgs PickerArgs; PickerArgs.bIsModal = false; PickerArgs.ParentWidget = ParentContent.Pin(); if ( SelectedPaths.Num() > 0 ) { // Make sure an color entry exists for all the paths, otherwise they won't update in realtime with the widget color for (int32 PathIdx = SelectedPaths.Num() - 1; PathIdx >= 0; --PathIdx) { const FString& Path = SelectedPaths[PathIdx]; TSharedPtr Color = ContentBrowserUtils::LoadColor( Path ); if ( !Color.IsValid() ) { Color = MakeShareable( new FLinearColor( ContentBrowserUtils::GetDefaultColor() ) ); ContentBrowserUtils::SaveColor( Path, Color, true ); } else { // Default the color to the first valid entry PickerArgs.InitialColorOverride = *Color.Get(); } LinearColorArray.Add( Color.Get() ); } PickerArgs.LinearColorArray = &LinearColorArray; } PickerArgs.OnColorPickerWindowClosed = FOnWindowClosed::CreateSP(this, &FPathContextMenu::NewColorComplete); OpenColorPicker(PickerArgs); } void FPathContextMenu::NewColorComplete(const TSharedRef& Window) { // Save the colors back in the config (ptr should have already updated by the widget) for (int32 PathIdx = 0; PathIdx < SelectedPaths.Num(); ++PathIdx) { const FString& Path = SelectedPaths[PathIdx]; const TSharedPtr Color = ContentBrowserUtils::LoadColor( Path ); check( Color.IsValid() ); ContentBrowserUtils::SaveColor( Path, Color ); } } FReply FPathContextMenu::OnColorClicked( const FLinearColor InColor ) { // Make sure an color entry exists for all the paths, otherwise it can't save correctly for (int32 PathIdx = 0; PathIdx < SelectedPaths.Num(); ++PathIdx) { const FString& Path = SelectedPaths[PathIdx]; TSharedPtr Color = ContentBrowserUtils::LoadColor( Path ); if ( !Color.IsValid() ) { Color = MakeShareable( new FLinearColor() ); } *Color.Get() = InColor; ContentBrowserUtils::SaveColor( Path, Color ); } // Dismiss the menu here, as we can't make the 'clear' option appear if a folder has just had a color set for the first time FSlateApplication::Get().DismissAllMenus(); return FReply::Handled(); } void FPathContextMenu::ResetColors() { // Clear the custom colors for all the selected paths for (int32 PathIdx = 0; PathIdx < SelectedPaths.Num(); ++PathIdx) { const FString& Path = SelectedPaths[PathIdx]; ContentBrowserUtils::SaveColor( Path, NULL ); } } void FPathContextMenu::ExecuteSaveFolder() { // Get a list of package names in the selected paths TArray PackageNames; GetPackageNamesInSelectedPaths(PackageNames); // Form a list of packages from the assets TArray Packages; for (int32 PackageIdx = 0; PackageIdx < PackageNames.Num(); ++PackageIdx) { UPackage* Package = FindPackage(NULL, *PackageNames[PackageIdx]); // Only save loaded and dirty packages if ( Package != NULL && Package->IsDirty() ) { Packages.Add(Package); } } // Save all packages that were found if ( Packages.Num() ) { ContentBrowserUtils::SavePackages(Packages); } } bool FPathContextMenu::CanExecuteDelete() const { int32 NumAssetPaths, NumClassPaths; ContentBrowserUtils::CountPathTypes(SelectedPaths, NumAssetPaths, NumClassPaths); // We can't delete folders containing classes return NumAssetPaths > 0 && NumClassPaths == 0; } void FPathContextMenu::ExecuteDelete() { check(SelectedPaths.Num() > 0); if (ParentContent.IsValid()) { FText Prompt; if ( SelectedPaths.Num() == 1 ) { Prompt = FText::Format(LOCTEXT("FolderDeleteConfirm_Single", "Delete folder '{0}'?"), FText::FromString(SelectedPaths[0])); } else { Prompt = FText::Format(LOCTEXT("FolderDeleteConfirm_Multiple", "Delete {0} folders?"), FText::AsNumber(SelectedPaths.Num())); } // Spawn a confirmation dialog since this is potentially a highly destructive operation FOnClicked OnYesClicked = FOnClicked::CreateSP( this, &FPathContextMenu::ExecuteDeleteFolderConfirmed ); ContentBrowserUtils::DisplayConfirmationPopup( Prompt, LOCTEXT("FolderDeleteConfirm_Yes", "Delete"), LOCTEXT("FolderDeleteConfirm_No", "Cancel"), ParentContent.Pin().ToSharedRef(), OnYesClicked); } } void FPathContextMenu::ExecuteReferenceViewer() { TArray PackageNamesAsStrings; GetPackageNamesInSelectedPaths(PackageNamesAsStrings); TArray PackageNames; for ( auto PackageNameIt = PackageNamesAsStrings.CreateConstIterator(); PackageNameIt; ++PackageNameIt ) { PackageNames.Add(**PackageNameIt); } if ( PackageNames.Num() > 0 ) { IReferenceViewerModule::Get().InvokeReferenceViewerTab(PackageNames); } } void FPathContextMenu::ExecuteSizeMap() { TArray PackageNamesAsStrings; GetPackageNamesInSelectedPaths(PackageNamesAsStrings); TArray PackageNames; for ( auto PackageNameIt = PackageNamesAsStrings.CreateConstIterator(); PackageNameIt; ++PackageNameIt ) { PackageNames.Add(**PackageNameIt); } if ( PackageNames.Num() > 0 ) { ISizeMapModule::Get().InvokeSizeMapTab(PackageNames); } } void FPathContextMenu::ExecuteFixUpRedirectorsInFolder() { FAssetRegistryModule& AssetRegistryModule = FModuleManager::Get().LoadModuleChecked(TEXT("AssetRegistry")); // Form a filter from the paths FARFilter Filter; Filter.bRecursivePaths = true; for (const auto& Path : SelectedPaths) { Filter.PackagePaths.Emplace(*Path); Filter.ClassNames.Emplace(TEXT("ObjectRedirector")); } // Query for a list of assets in the selected paths TArray AssetList; AssetRegistryModule.Get().GetAssets(Filter, AssetList); if (AssetList.Num() > 0) { TArray ObjectPaths; for (const auto& Asset : AssetList) { ObjectPaths.Add(Asset.ObjectPath.ToString()); } TArray Objects; const bool bAllowedToPromptToLoadAssets = true; const bool bLoadRedirects = true; if (ContentBrowserUtils::LoadAssetsIfNeeded(ObjectPaths, Objects, bAllowedToPromptToLoadAssets, bLoadRedirects)) { // Transform Objects array to ObjectRedirectors array TArray Redirectors; for (auto Object : Objects) { auto Redirector = CastChecked(Object); Redirectors.Add(Redirector); } // Load the asset tools module FAssetToolsModule& AssetToolsModule = FModuleManager::LoadModuleChecked(TEXT("AssetTools")); AssetToolsModule.Get().FixupReferencers(Redirectors); } } } FReply FPathContextMenu::ExecuteDeleteFolderConfirmed() { if ( ContentBrowserUtils::DeleteFolders(SelectedPaths) ) { ResetColors(); if (OnFolderDeleted.IsBound()) { OnFolderDeleted.Execute(); } } return FReply::Handled(); } void FPathContextMenu::ExecuteSCCCheckOut() { // Get a list of package names in the selected paths TArray PackageNames; GetPackageNamesInSelectedPaths(PackageNames); TArray PackagesToCheckOut; for ( auto PackageIt = PackageNames.CreateConstIterator(); PackageIt; ++PackageIt ) { if ( FPackageName::DoesPackageExist(*PackageIt) ) { // Since the file exists, create the package if it isn't loaded or just find the one that is already loaded // No need to load unloaded packages. It isn't needed for the checkout process UPackage* Package = CreatePackage(NULL, **PackageIt); PackagesToCheckOut.Add( CreatePackage(NULL, **PackageIt) ); } } if ( PackagesToCheckOut.Num() > 0 ) { // Update the source control status of all potentially relevant packages ISourceControlModule::Get().GetProvider().Execute(ISourceControlOperation::Create(), PackagesToCheckOut); // Now check them out FEditorFileUtils::CheckoutPackages(PackagesToCheckOut); } } void FPathContextMenu::ExecuteSCCOpenForAdd() { ISourceControlProvider& SourceControlProvider = ISourceControlModule::Get().GetProvider(); // Get a list of package names in the selected paths TArray PackageNames; GetPackageNamesInSelectedPaths(PackageNames); TArray PackagesToAdd; TArray PackagesToSave; for ( auto PackageIt = PackageNames.CreateConstIterator(); PackageIt; ++PackageIt ) { FSourceControlStatePtr SourceControlState = SourceControlProvider.GetState(SourceControlHelpers::PackageFilename(*PackageIt), EStateCacheUsage::Use); if ( SourceControlState.IsValid() && !SourceControlState->IsSourceControlled() ) { PackagesToAdd.Add(*PackageIt); // Make sure the file actually exists on disk before adding it FString Filename; if ( !FPackageName::DoesPackageExist(*PackageIt, NULL, &Filename) ) { UPackage* Package = FindPackage(NULL, **PackageIt); if ( Package ) { PackagesToSave.Add(Package); } } } } if ( PackagesToAdd.Num() > 0 ) { // If any of the packages are new, save them now if ( PackagesToSave.Num() > 0 ) { const bool bCheckDirty = false; const bool bPromptToSave = false; TArray FailedPackages; const FEditorFileUtils::EPromptReturnCode Return = FEditorFileUtils::PromptForCheckoutAndSave(PackagesToSave, bCheckDirty, bPromptToSave, &FailedPackages); if(FailedPackages.Num() > 0) { // don't try and add files that failed to save - remove them from the list for(auto FailedPackageIt = FailedPackages.CreateConstIterator(); FailedPackageIt; FailedPackageIt++) { PackagesToAdd.Remove((*FailedPackageIt)->GetName()); } } } if ( PackagesToAdd.Num() > 0 ) { SourceControlProvider.Execute(ISourceControlOperation::Create(), SourceControlHelpers::PackageFilenames(PackagesToAdd)); } } } void FPathContextMenu::ExecuteSCCCheckIn() { // Get a list of package names in the selected paths TArray PackageNames; GetPackageNamesInSelectedPaths(PackageNames); // Form a list of loaded packages to prompt for save TArray LoadedPackages; for ( auto PackageIt = PackageNames.CreateConstIterator(); PackageIt; ++PackageIt ) { UPackage* Package = FindPackage(NULL, **PackageIt); if ( Package ) { LoadedPackages.Add(Package); } } // Prompt the user to ask if they would like to first save any dirty packages they are trying to check-in const FEditorFileUtils::EPromptReturnCode UserResponse = FEditorFileUtils::PromptForCheckoutAndSave( LoadedPackages, true, true ); // If the user elected to save dirty packages, but one or more of the packages failed to save properly OR if the user // canceled out of the prompt, don't follow through on the check-in process const bool bShouldProceed = ( UserResponse == FEditorFileUtils::EPromptReturnCode::PR_Success || UserResponse == FEditorFileUtils::EPromptReturnCode::PR_Declined ); if ( bShouldProceed ) { TArray PendingDeletePaths; for (const auto& Path : SelectedPaths) { PendingDeletePaths.Add(FPaths::ConvertRelativePathToFull(FPackageName::LongPackageNameToFilename(Path + TEXT("/")))); } const bool bUseSourceControlStateCache = false; FSourceControlWindows::PromptForCheckin(bUseSourceControlStateCache, PackageNames, PendingDeletePaths); } else { // If a failure occurred, alert the user that the check-in was aborted. This warning shouldn't be necessary if the user cancelled // from the dialog, because they obviously intended to cancel the whole operation. if ( UserResponse == FEditorFileUtils::EPromptReturnCode::PR_Failure ) { FMessageDialog::Open( EAppMsgType::Ok, NSLOCTEXT("UnrealEd", "SCC_Checkin_Aborted", "Check-in aborted as a result of save failure.") ); } } } void FPathContextMenu::ExecuteSCCSync() const { ISourceControlProvider& SourceControlProvider = ISourceControlModule::Get().GetProvider(); TArray RootPaths; FPackageName::QueryRootContentPaths( RootPaths ); // find valid paths on disk TArray PathsOnDisk; for(const auto& SelectedPath : SelectedPaths) { // note: we make sure to terminate our path here so root directories map correctly. const FString CompletePath = SelectedPath / ""; const bool bMatchesRoot = RootPaths.ContainsByPredicate([&](const FString& RootPath){ return CompletePath.StartsWith(RootPath); }); if(bMatchesRoot) { FString PathOnDisk = FPackageName::LongPackageNameToFilename(CompletePath); PathsOnDisk.Add(PathOnDisk); } } if ( PathsOnDisk.Num() > 0 ) { // attempt to unload all assets under this path TArray PackageNames; GetPackageNamesInSelectedPaths(PackageNames); // Form a list of loaded packages to prompt for save TArray LoadedPackages; for( const auto& PackageName : PackageNames ) { UPackage* Package = FindPackage(nullptr, *PackageName); if ( Package != nullptr ) { LoadedPackages.Add(Package); } } FText ErrorMessage; PackageTools::UnloadPackages(LoadedPackages, ErrorMessage); if(!ErrorMessage.IsEmpty()) { FMessageDialog::Open( EAppMsgType::Ok, ErrorMessage ); } else { SourceControlProvider.Execute(ISourceControlOperation::Create(), PathsOnDisk); } } else { UE_LOG(LogContentBrowser, Warning, TEXT("Couldn't find any valid paths to sync.")) } } void FPathContextMenu::ExecuteSCCConnect() const { ISourceControlModule::Get().ShowLoginDialog(FSourceControlLoginClosed(), ELoginWindowMode::Modeless); } bool FPathContextMenu::CanExecuteSCCCheckOut() const { int32 NumAssetPaths, NumClassPaths; ContentBrowserUtils::CountPathTypes(SelectedPaths, NumAssetPaths, NumClassPaths); // Can only perform SCC operations on asset paths return bCanExecuteSCCCheckOut && (NumAssetPaths > 0 && NumClassPaths == 0); } bool FPathContextMenu::CanExecuteSCCOpenForAdd() const { int32 NumAssetPaths, NumClassPaths; ContentBrowserUtils::CountPathTypes(SelectedPaths, NumAssetPaths, NumClassPaths); // Can only perform SCC operations on asset paths return bCanExecuteSCCOpenForAdd && (NumAssetPaths > 0 && NumClassPaths == 0); } bool FPathContextMenu::CanExecuteSCCCheckIn() const { int32 NumAssetPaths, NumClassPaths; ContentBrowserUtils::CountPathTypes(SelectedPaths, NumAssetPaths, NumClassPaths); // Can only perform SCC operations on asset paths return bCanExecuteSCCCheckIn && (NumAssetPaths > 0 && NumClassPaths == 0); } bool FPathContextMenu::CanExecuteSCCSync() const { int32 NumAssetPaths, NumClassPaths; ContentBrowserUtils::CountPathTypes(SelectedPaths, NumAssetPaths, NumClassPaths); // Can only perform SCC operations on asset paths return (NumAssetPaths > 0 && NumClassPaths == 0); } bool FPathContextMenu::CanExecuteSCCConnect() const { int32 NumAssetPaths, NumClassPaths; ContentBrowserUtils::CountPathTypes(SelectedPaths, NumAssetPaths, NumClassPaths); // Can only perform SCC operations on asset paths return (!ISourceControlModule::Get().IsEnabled() || !ISourceControlModule::Get().GetProvider().IsAvailable()) && (NumAssetPaths > 0 && NumClassPaths == 0); } void FPathContextMenu::CacheCanExecuteVars() { // Cache whether we can execute any of the source control commands bCanExecuteSCCCheckOut = false; bCanExecuteSCCOpenForAdd = false; bCanExecuteSCCCheckIn = false; ISourceControlProvider& SourceControlProvider = ISourceControlModule::Get().GetProvider(); if ( SourceControlProvider.IsEnabled() && SourceControlProvider.IsAvailable() ) { TArray PackageNames; GetPackageNamesInSelectedPaths(PackageNames); // Check the SCC state for each package in the selected paths for ( auto PackageIt = PackageNames.CreateConstIterator(); PackageIt; ++PackageIt ) { FSourceControlStatePtr SourceControlState = SourceControlProvider.GetState(SourceControlHelpers::PackageFilename(*PackageIt), EStateCacheUsage::Use); if(SourceControlState.IsValid()) { if ( SourceControlState->CanCheckout() ) { bCanExecuteSCCCheckOut = true; } else if ( !SourceControlState->IsSourceControlled() ) { bCanExecuteSCCOpenForAdd = true; } else if ( SourceControlState->CanCheckIn() ) { bCanExecuteSCCCheckIn = true; } } if ( bCanExecuteSCCCheckOut && bCanExecuteSCCOpenForAdd && bCanExecuteSCCCheckIn ) { // All SCC options are available, no need to keep iterating break; } } } } void FPathContextMenu::GetPackageNamesInSelectedPaths(TArray& OutPackageNames) const { // Load the asset registry module FAssetRegistryModule& AssetRegistryModule = FModuleManager::Get().LoadModuleChecked(TEXT("AssetRegistry")); // Form a filter from the paths FARFilter Filter; Filter.bRecursivePaths = true; for (int32 PathIdx = 0; PathIdx < SelectedPaths.Num(); ++PathIdx) { const FString& Path = SelectedPaths[PathIdx]; new (Filter.PackagePaths) FName(*Path); } // Query for a list of assets in the selected paths TArray AssetList; AssetRegistryModule.Get().GetAssets(Filter, AssetList); // Form a list of unique package names from the assets TSet UniquePackageNames; for (int32 AssetIdx = 0; AssetIdx < AssetList.Num(); ++AssetIdx) { UniquePackageNames.Add(AssetList[AssetIdx].PackageName); } // Add all unique package names to the output for ( auto PackageIt = UniquePackageNames.CreateConstIterator(); PackageIt; ++PackageIt ) { OutPackageNames.Add( (*PackageIt).ToString() ); } } FString FPathContextMenu::GetFirstSelectedPath() const { return SelectedPaths.Num() > 0 ? SelectedPaths[0] : TEXT(""); } bool FPathContextMenu::SelectedHasCustomColors() const { for (int32 PathIdx = 0; PathIdx < SelectedPaths.Num(); ++PathIdx) { // Ignore any that are the default color const FString& Path = SelectedPaths[PathIdx]; const TSharedPtr Color = ContentBrowserUtils::LoadColor( Path ); if ( Color.IsValid() && !Color->Equals( ContentBrowserUtils::GetDefaultColor() ) ) { return true; } } return false; } #undef LOCTEXT_NAMESPACE