// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. #include "AssetContextMenu.h" #include "Templates/SubclassOf.h" #include "Styling/SlateTypes.h" #include "Framework/Commands/UIAction.h" #include "Textures/SlateIcon.h" #include "Engine/Blueprint.h" #include "Misc/MessageDialog.h" #include "HAL/PlatformApplicationMisc.h" #include "HAL/FileManager.h" #include "Misc/ScopedSlowTask.h" #include "UObject/MetaData.h" #include "UObject/UObjectIterator.h" #include "Widgets/SBoxPanel.h" #include "Widgets/SWindow.h" #include "Framework/Application/SlateApplication.h" #include "Widgets/Text/STextBlock.h" #include "Widgets/Input/SMultiLineEditableTextBox.h" #include "Framework/MultiBox/MultiBoxBuilder.h" #include "Widgets/Input/SButton.h" #include "EditorStyleSet.h" #include "EditorReimportHandler.h" #include "Components/ActorComponent.h" #include "GameFramework/Actor.h" #include "UnrealClient.h" #include "Materials/MaterialFunctionInstance.h" #include "Materials/Material.h" #include "SourceControlOperations.h" #include "ISourceControlModule.h" #include "SourceControlHelpers.h" #include "Settings/EditorExperimentalSettings.h" #include "Materials/MaterialInstanceConstant.h" #include "FileHelpers.h" #include "AssetRegistryModule.h" #include "IAssetTools.h" #include "AssetToolsModule.h" #include "ContentBrowserUtils.h" #include "SAssetView.h" #include "ContentBrowserModule.h" #include "Dialogs/Dialogs.h" #include "SMetaDataView.h" #include "ObjectTools.h" #include "PackageTools.h" #include "Editor.h" #include "Toolkits/AssetEditorManager.h" #include "PropertyEditorModule.h" #include "Toolkits/GlobalEditorCommonCommands.h" #include "ConsolidateWindow.h" #include "ReferencedAssetsUtils.h" #include "Internationalization/PackageLocalizationUtil.h" #include "Internationalization/TextLocalizationResource.h" #include "SourceControlWindows.h" #include "Kismet2/KismetEditorUtilities.h" #include "CollectionAssetManagement.h" #include "ComponentAssetBroker.h" #include "Widgets/Input/SNumericEntryBox.h" #include "SourceCodeNavigation.h" #include "IDocumentation.h" #include "EditorClassUtils.h" #include "Internationalization/Culture.h" #include "Internationalization/TextPackageNamespaceUtil.h" #include "Widgets/Colors/SColorPicker.h" #include "Framework/Commands/GenericCommands.h" #include "Framework/Notifications/NotificationManager.h" #include "Widgets/Notifications/SNotificationList.h" #include "Engine/LevelStreaming.h" #include "ContentBrowserCommands.h" #include "PackageHelperFunctions.h" #include "EngineUtils.h" #define LOCTEXT_NAMESPACE "ContentBrowser" FAssetContextMenu::FAssetContextMenu(const TWeakPtr& InAssetView) : AssetView(InAssetView) , bAtLeastOneNonRedirectorSelected(false) , bAtLeastOneClassSelected(false) , bCanExecuteSCCMerge(false) , bCanExecuteSCCCheckOut(false) , bCanExecuteSCCOpenForAdd(false) , bCanExecuteSCCCheckIn(false) , bCanExecuteSCCHistory(false) , bCanExecuteSCCRevert(false) , bCanExecuteSCCSync(false) { } void FAssetContextMenu::BindCommands(TSharedPtr< FUICommandList >& Commands) { Commands->MapAction(FGenericCommands::Get().Duplicate, FUIAction( FExecuteAction::CreateSP(this, &FAssetContextMenu::ExecuteDuplicate), FCanExecuteAction::CreateSP(this, &FAssetContextMenu::CanExecuteDuplicate), FIsActionChecked(), FIsActionButtonVisible::CreateSP(this, &FAssetContextMenu::CanExecuteDuplicate) )); Commands->MapAction(FGlobalEditorCommonCommands::Get().FindInContentBrowser, FUIAction( FExecuteAction::CreateSP(this, &FAssetContextMenu::ExecuteSyncToAssetTree), FCanExecuteAction::CreateSP(this, &FAssetContextMenu::CanExecuteSyncToAssetTree) )); } TSharedRef FAssetContextMenu::MakeContextMenu(const TArray& InSelectedAssets, const FSourcesData& InSourcesData, TSharedPtr< FUICommandList > InCommandList) { SetSelectedAssets(InSelectedAssets); SourcesData = InSourcesData; // 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.GetAllAssetViewContextMenuExtenders(); TArray> Extenders; for (int32 i = 0; i < MenuExtenderDelegates.Num(); ++i) { if (MenuExtenderDelegates[i].IsBound()) { Extenders.Add(MenuExtenderDelegates[i].Execute(SelectedAssets)); } } TSharedPtr MenuExtender = FExtender::Combine(Extenders); FMenuBuilder MenuBuilder(/*bInShouldCloseWindowAfterMenuSelection=*/true, InCommandList, MenuExtender); // Only add something if at least one asset is selected if ( SelectedAssets.Num() ) { // Add any type-specific context menu options AddAssetTypeMenuOptions(MenuBuilder); // Add imported asset context menu options AddImportedAssetMenuOptions(MenuBuilder); // Add quick access to common commands. AddCommonMenuOptions(MenuBuilder); // Add quick access to view commands AddExploreMenuOptions(MenuBuilder); // Add reference options AddReferenceMenuOptions(MenuBuilder); // Add collection options AddCollectionMenuOptions(MenuBuilder); // Add documentation options AddDocumentationMenuOptions(MenuBuilder); // Add source control options AddSourceControlMenuOptions(MenuBuilder); } return MenuBuilder.MakeWidget(); } void FAssetContextMenu::SetSelectedAssets(const TArray& InSelectedAssets) { SelectedAssets = InSelectedAssets; } void FAssetContextMenu::SetOnFindInAssetTreeRequested(const FOnFindInAssetTreeRequested& InOnFindInAssetTreeRequested) { OnFindInAssetTreeRequested = InOnFindInAssetTreeRequested; } void FAssetContextMenu::SetOnRenameRequested(const FOnRenameRequested& InOnRenameRequested) { OnRenameRequested = InOnRenameRequested; } void FAssetContextMenu::SetOnRenameFolderRequested(const FOnRenameFolderRequested& InOnRenameFolderRequested) { OnRenameFolderRequested = InOnRenameFolderRequested; } void FAssetContextMenu::SetOnDuplicateRequested(const FOnDuplicateRequested& InOnDuplicateRequested) { OnDuplicateRequested = InOnDuplicateRequested; } void FAssetContextMenu::SetOnAssetViewRefreshRequested(const FOnAssetViewRefreshRequested& InOnAssetViewRefreshRequested) { OnAssetViewRefreshRequested = InOnAssetViewRefreshRequested; } bool FAssetContextMenu::AddImportedAssetMenuOptions(FMenuBuilder& MenuBuilder) { if (AreImportedAssetActionsVisible()) { TArray ResolvedFilePaths; TArray SourceFileLabels; int32 ValidSelectedAssetCount = 0; GetSelectedAssetSourceFilePaths(ResolvedFilePaths, SourceFileLabels, ValidSelectedAssetCount); MenuBuilder.BeginSection("ImportedAssetActions", LOCTEXT("ImportedAssetActionsMenuHeading", "Imported Asset")); { auto CreateSubMenu = [this](FMenuBuilder& SubMenuBuilder, bool bReimportWithNewFile) { //Get the data, we cannot use the closure since the lambda will be call when the function scope will be gone TArray ResolvedFilePaths; TArray SourceFileLabels; int32 ValidSelectedAssetCount = 0; GetSelectedAssetSourceFilePaths(ResolvedFilePaths, SourceFileLabels, ValidSelectedAssetCount); if (SourceFileLabels.Num() > 0 ) { for (int32 SourceFileIndex = 0; SourceFileIndex < SourceFileLabels.Num(); ++SourceFileIndex) { FText ReimportLabel = FText::Format(LOCTEXT("ReimportNoLabel", "SourceFile {0}"), SourceFileIndex); FText ReimportLabelTooltip; if (ValidSelectedAssetCount == 1) { ReimportLabelTooltip = FText::Format(LOCTEXT("ReimportNoLabelTooltip", "Reimport File: {0}"), FText::FromString(ResolvedFilePaths[SourceFileIndex])); } if (SourceFileLabels[SourceFileIndex].Len() > 0) { ReimportLabel = FText::Format(LOCTEXT("ReimportLabel", "{0}"), FText::FromString(SourceFileLabels[SourceFileIndex])); if (ValidSelectedAssetCount == 1) { ReimportLabelTooltip = FText::Format(LOCTEXT("ReimportLabelTooltip", "Reimport {0} File: {1}"), FText::FromString(SourceFileLabels[SourceFileIndex]), FText::FromString(ResolvedFilePaths[SourceFileIndex])); } } if (bReimportWithNewFile) { SubMenuBuilder.AddMenuEntry( ReimportLabel, ReimportLabelTooltip, FSlateIcon(FEditorStyle::GetStyleSetName(), "ContentBrowser.AssetActions.ReimportAsset"), FUIAction( FExecuteAction::CreateSP(this, &FAssetContextMenu::ExecuteReimportWithNewFile, SourceFileIndex), FCanExecuteAction() ) ); } else { SubMenuBuilder.AddMenuEntry( ReimportLabel, ReimportLabelTooltip, FSlateIcon(FEditorStyle::GetStyleSetName(), "ContentBrowser.AssetActions.ReimportAsset"), FUIAction( FExecuteAction::CreateSP(this, &FAssetContextMenu::ExecuteReimport, SourceFileIndex), FCanExecuteAction() ) ); } } } }; //Reimport Menu if (ValidSelectedAssetCount == 1 && SourceFileLabels.Num() > 1) { MenuBuilder.AddSubMenu( LOCTEXT("Reimport", "Reimport"), LOCTEXT("ReimportEmptyTooltip", ""), FNewMenuDelegate::CreateLambda(CreateSubMenu, false) ); //With new file MenuBuilder.AddSubMenu( LOCTEXT("ReimportWithNewFile", "Reimport With New File"), LOCTEXT("ReimportEmptyTooltip", ""), FNewMenuDelegate::CreateLambda(CreateSubMenu, true)); } else { MenuBuilder.AddMenuEntry( LOCTEXT("Reimport", "Reimport"), LOCTEXT("ReimportTooltip", "Reimport the selected asset(s) from the source file on disk."), FSlateIcon(FEditorStyle::GetStyleSetName(), "ContentBrowser.AssetActions.ReimportAsset"), FUIAction( FExecuteAction::CreateSP(this, &FAssetContextMenu::ExecuteReimport, (int32)INDEX_NONE), FCanExecuteAction() ) ); if (ValidSelectedAssetCount == 1) { //With new file MenuBuilder.AddMenuEntry( LOCTEXT("ReimportWithNewFile", "Reimport With New File"), LOCTEXT("ReimportWithNewFileTooltip", "Reimport the selected asset from a new source file on disk."), FSlateIcon(FEditorStyle::GetStyleSetName(), "ContentBrowser.AssetActions.ReimportAsset"), FUIAction( FExecuteAction::CreateSP(this, &FAssetContextMenu::ExecuteReimportWithNewFile, (int32)INDEX_NONE), FCanExecuteAction() ) ); } } // Show Source In Explorer MenuBuilder.AddMenuEntry( LOCTEXT("FindSourceFile", "Open Source Location"), LOCTEXT("FindSourceFileTooltip", "Opens the folder containing the source of the selected asset(s)."), FSlateIcon(FEditorStyle::GetStyleSetName(), "ContentBrowser.AssetActions.OpenSourceLocation"), FUIAction( FExecuteAction::CreateSP(this, &FAssetContextMenu::ExecuteFindSourceInExplorer, ResolvedFilePaths), FCanExecuteAction::CreateSP(this, &FAssetContextMenu::CanExecuteImportedAssetActions, ResolvedFilePaths) ) ); // Open In External Editor MenuBuilder.AddMenuEntry( LOCTEXT("OpenInExternalEditor", "Open In External Editor"), LOCTEXT("OpenInExternalEditorTooltip", "Open the selected asset(s) in the default external editor."), FSlateIcon(FEditorStyle::GetStyleSetName(), "ContentBrowser.AssetActions.OpenInExternalEditor"), FUIAction( FExecuteAction::CreateSP(this, &FAssetContextMenu::ExecuteOpenInExternalEditor, ResolvedFilePaths), FCanExecuteAction::CreateSP(this, &FAssetContextMenu::CanExecuteImportedAssetActions, ResolvedFilePaths) ) ); } MenuBuilder.EndSection(); return true; } return false; } bool FAssetContextMenu::AddCommonMenuOptions(FMenuBuilder& MenuBuilder) { int32 NumAssetItems, NumClassItems; ContentBrowserUtils::CountItemTypes(SelectedAssets, NumAssetItems, NumClassItems); MenuBuilder.BeginSection("CommonAssetActions", LOCTEXT("CommonAssetActionsMenuHeading", "Common")); { // Edit MenuBuilder.AddMenuEntry( LOCTEXT("EditAsset", "Edit..."), LOCTEXT("EditAssetTooltip", "Opens the selected asset(s) for edit."), FSlateIcon(FEditorStyle::GetStyleSetName(), "ContentBrowser.AssetActions.Edit"), FUIAction( FExecuteAction::CreateSP(this, &FAssetContextMenu::ExecuteEditAsset) ) ); // Only add these options if assets are selected if (NumAssetItems > 0) { // Rename MenuBuilder.AddMenuEntry(FGenericCommands::Get().Rename, NAME_None, LOCTEXT("Rename", "Rename"), LOCTEXT("RenameTooltip", "Rename the selected asset."), FSlateIcon(FEditorStyle::GetStyleSetName(), "ContentBrowser.AssetActions.Rename") ); // Duplicate MenuBuilder.AddMenuEntry(FGenericCommands::Get().Duplicate, NAME_None, LOCTEXT("Duplicate", "Duplicate"), LOCTEXT("DuplicateTooltip", "Create a copy of the selected asset(s)."), FSlateIcon(FEditorStyle::GetStyleSetName(), "ContentBrowser.AssetActions.Duplicate") ); // Save MenuBuilder.AddMenuEntry(FContentBrowserCommands::Get().SaveSelectedAsset, NAME_None, LOCTEXT("SaveAsset", "Save"), LOCTEXT("SaveAssetTooltip", "Saves the asset to file."), FSlateIcon(FEditorStyle::GetStyleSetName(), "Level.SaveIcon16x") ); // Delete MenuBuilder.AddMenuEntry(FGenericCommands::Get().Delete, NAME_None, LOCTEXT("Delete", "Delete"), LOCTEXT("DeleteTooltip", "Delete the selected assets."), FSlateIcon(FEditorStyle::GetStyleSetName(), "ContentBrowser.AssetActions.Delete") ); // Asset Actions sub-menu MenuBuilder.AddSubMenu( LOCTEXT("AssetActionsSubMenuLabel", "Asset Actions"), LOCTEXT("AssetActionsSubMenuToolTip", "Other asset actions"), FNewMenuDelegate::CreateSP(this, &FAssetContextMenu::MakeAssetActionsSubMenu), FUIAction( FExecuteAction(), FCanExecuteAction::CreateSP( this, &FAssetContextMenu::CanExecuteAssetActions ) ), NAME_None, EUserInterfaceActionType::Button, false, FSlateIcon(FEditorStyle::GetStyleSetName(), "ContentBrowser.AssetActions") ); if (NumClassItems == 0) { // Asset Localization sub-menu MenuBuilder.AddSubMenu( LOCTEXT("LocalizationSubMenuLabel", "Asset Localization"), LOCTEXT("LocalizationSubMenuToolTip", "Manage the localization of this asset"), FNewMenuDelegate::CreateSP(this, &FAssetContextMenu::MakeAssetLocalizationSubMenu), FUIAction(), NAME_None, EUserInterfaceActionType::Button, false, FSlateIcon(FEditorStyle::GetStyleSetName(), "ContentBrowser.AssetLocalization") ); } } } MenuBuilder.EndSection(); return true; } void FAssetContextMenu::AddExploreMenuOptions(FMenuBuilder& MenuBuilder) { MenuBuilder.BeginSection("AssetContextExploreMenuOptions", LOCTEXT("AssetContextExploreMenuOptionsHeading", "Explore")); { // Find in Content Browser MenuBuilder.AddMenuEntry( FGlobalEditorCommonCommands::Get().FindInContentBrowser, NAME_None, LOCTEXT("ShowInFolderView", "Show in Folder View"), LOCTEXT("ShowInFolderViewTooltip", "Selects the folder that contains this asset in the Content Browser Sources Panel.") ); // Find in Explorer MenuBuilder.AddMenuEntry( ContentBrowserUtils::GetExploreFolderText(), LOCTEXT("FindInExplorerTooltip", "Finds this asset on disk"), FSlateIcon(FEditorStyle::GetStyleSetName(), "SystemWideCommands.FindInContentBrowser"), FUIAction( FExecuteAction::CreateSP( this, &FAssetContextMenu::ExecuteFindInExplorer ), FCanExecuteAction::CreateSP( this, &FAssetContextMenu::CanExecuteFindInExplorer ) ) ); } MenuBuilder.EndSection(); } void FAssetContextMenu::MakeAssetActionsSubMenu(FMenuBuilder& MenuBuilder) { // Create BP Using This MenuBuilder.AddMenuEntry( LOCTEXT("CreateBlueprintUsing", "Create Blueprint Using This..."), LOCTEXT("CreateBlueprintUsingTooltip", "Create a new Blueprint and add this asset to it"), FSlateIcon(FEditorStyle::GetStyleSetName(), "LevelEditor.CreateClassBlueprint"), FUIAction( FExecuteAction::CreateSP(this, &FAssetContextMenu::ExecuteCreateBlueprintUsing), FCanExecuteAction::CreateSP(this, &FAssetContextMenu::CanExecuteCreateBlueprintUsing) ) ); // Capture Thumbnail FAssetToolsModule& AssetToolsModule = FModuleManager::LoadModuleChecked("AssetTools"); if (SelectedAssets.Num() == 1 && AssetToolsModule.Get().AssetUsesGenericThumbnail(SelectedAssets[0])) { MenuBuilder.AddMenuEntry( LOCTEXT("CaptureThumbnail", "Capture Thumbnail"), LOCTEXT("CaptureThumbnailTooltip", "Captures a thumbnail from the active viewport."), FSlateIcon(FEditorStyle::GetStyleSetName(), "ContentBrowser.AssetActions.CreateThumbnail"), FUIAction( FExecuteAction::CreateSP(this, &FAssetContextMenu::ExecuteCaptureThumbnail), FCanExecuteAction::CreateSP(this, &FAssetContextMenu::CanExecuteCaptureThumbnail) ) ); } // Clear Thumbnail if (CanClearCustomThumbnails()) { MenuBuilder.AddMenuEntry( LOCTEXT("ClearCustomThumbnail", "Clear Thumbnail"), LOCTEXT("ClearCustomThumbnailTooltip", "Clears all custom thumbnails for selected assets."), FSlateIcon(FEditorStyle::GetStyleSetName(), "ContentBrowser.AssetActions.DeleteThumbnail"), FUIAction( FExecuteAction::CreateSP(this, &FAssetContextMenu::ExecuteClearThumbnail) ) ); } // FIND ACTIONS MenuBuilder.BeginSection("AssetContextFindActions", LOCTEXT("AssetContextFindActionsMenuHeading", "Find")); { // Select Actors Using This Asset MenuBuilder.AddMenuEntry( LOCTEXT("FindAssetInWorld", "Select Actors Using This Asset"), LOCTEXT("FindAssetInWorldTooltip", "Selects all actors referencing this asset."), FSlateIcon(), FUIAction( FExecuteAction::CreateSP(this, &FAssetContextMenu::ExecuteFindAssetInWorld), FCanExecuteAction::CreateSP(this, &FAssetContextMenu::CanExecuteFindAssetInWorld) ) ); } MenuBuilder.EndSection(); // MOVE ACTIONS MenuBuilder.BeginSection("AssetContextMoveActions", LOCTEXT("AssetContextMoveActionsMenuHeading", "Move")); { bool bHasExportableAssets = false; for (const FAssetData& AssetData : SelectedAssets) { const UObject* Object = AssetData.GetAsset(); if (Object) { const UPackage* Package = Object->GetOutermost(); if (!Package->HasAnyPackageFlags(EPackageFlags::PKG_DisallowExport)) { bHasExportableAssets = true; break; } } } if (bHasExportableAssets) { // Export MenuBuilder.AddMenuEntry( LOCTEXT("Export", "Export..."), LOCTEXT("ExportTooltip", "Export the selected assets to file."), FSlateIcon(), FUIAction( FExecuteAction::CreateSP( this, &FAssetContextMenu::ExecuteExport ) ) ); // Bulk Export if (SelectedAssets.Num() > 1) { MenuBuilder.AddMenuEntry( LOCTEXT("BulkExport", "Bulk Export..."), LOCTEXT("BulkExportTooltip", "Export the selected assets to file in the selected directory"), FSlateIcon(), FUIAction( FExecuteAction::CreateSP( this, &FAssetContextMenu::ExecuteBulkExport ) ) ); } } // Migrate MenuBuilder.AddMenuEntry( LOCTEXT("MigrateAsset", "Migrate..."), LOCTEXT("MigrateAssetTooltip", "Copies all selected assets and their dependencies to another project"), FSlateIcon(), FUIAction( FExecuteAction::CreateSP( this, &FAssetContextMenu::ExecuteMigrateAsset ) ) ); } MenuBuilder.EndSection(); // ADVANCED ACTIONS MenuBuilder.BeginSection("AssetContextAdvancedActions", LOCTEXT("AssetContextAdvancedActionsMenuHeading", "Advanced")); { // Reload MenuBuilder.AddMenuEntry( LOCTEXT("Reload", "Reload"), LOCTEXT("ReloadTooltip", "Reload the selected assets from their file on disk."), FSlateIcon(), FUIAction( FExecuteAction::CreateSP(this, &FAssetContextMenu::ExecuteReload), FCanExecuteAction::CreateSP(this, &FAssetContextMenu::CanExecuteReload) ) ); // Replace References if (CanExecuteConsolidate()) { MenuBuilder.AddMenuEntry( LOCTEXT("ReplaceReferences", "Replace References"), LOCTEXT("ConsolidateTooltip", "Replace references to the selected assets."), FSlateIcon(), FUIAction( FExecuteAction::CreateSP(this, &FAssetContextMenu::ExecuteConsolidate) ) ); } // Property Matrix bool bCanUsePropertyMatrix = true; // Materials can't be bulk edited currently as they require very special handling because of their dependencies with the rendering thread, and we'd have to hack the property matrix too much. for (auto& Asset : SelectedAssets) { if (Asset.AssetClass == UMaterial::StaticClass()->GetFName() || Asset.AssetClass == UMaterialInstanceConstant::StaticClass()->GetFName() || Asset.AssetClass == UMaterialFunction::StaticClass()->GetFName() || Asset.AssetClass == UMaterialFunctionInstance::StaticClass()->GetFName()) { bCanUsePropertyMatrix = false; break; } } if (bCanUsePropertyMatrix) { TAttribute::FGetter DynamicTooltipGetter; DynamicTooltipGetter.BindSP(this, &FAssetContextMenu::GetExecutePropertyMatrixTooltip); TAttribute DynamicTooltipAttribute = TAttribute::Create(DynamicTooltipGetter); MenuBuilder.AddMenuEntry( LOCTEXT("PropertyMatrix", "Bulk Edit via Property Matrix..."), DynamicTooltipAttribute, FSlateIcon(), FUIAction( FExecuteAction::CreateSP(this, &FAssetContextMenu::ExecutePropertyMatrix), FCanExecuteAction::CreateSP(this, &FAssetContextMenu::CanExecutePropertyMatrix) ) ); } // Create Metadata menu MenuBuilder.AddMenuEntry( LOCTEXT("ShowAssetMetaData", "Show Metadata"), LOCTEXT("ShowAssetMetaDataTooltip", "Show the asset metadata dialog."), FSlateIcon(), FUIAction( FExecuteAction::CreateSP(this, &FAssetContextMenu::ExecuteShowAssetMetaData), FCanExecuteAction::CreateSP(this, &FAssetContextMenu::CanExecuteShowAssetMetaData) ) ); // Chunk actions if (GetDefault()->bContextMenuChunkAssignments) { MenuBuilder.AddMenuEntry( LOCTEXT("AssignAssetChunk", "Assign to Chunk..."), LOCTEXT("AssignAssetChunkTooltip", "Assign this asset to a specific Chunk"), FSlateIcon(), FUIAction( FExecuteAction::CreateSP(this, &FAssetContextMenu::ExecuteAssignChunkID) ) ); MenuBuilder.AddSubMenu( LOCTEXT("RemoveAssetFromChunk", "Remove from Chunk..."), LOCTEXT("RemoveAssetFromChunkTooltip", "Removed an asset from a Chunk it's assigned to."), FNewMenuDelegate::CreateRaw(this, &FAssetContextMenu::MakeChunkIDListMenu) ); MenuBuilder.AddMenuEntry( LOCTEXT("RemoveAllChunkAssignments", "Remove from all Chunks"), LOCTEXT("RemoveAllChunkAssignmentsTooltip", "Removed an asset from all Chunks it's assigned to."), FSlateIcon(), FUIAction( FExecuteAction::CreateSP(this, &FAssetContextMenu::ExecuteRemoveAllChunkID) ) ); } } MenuBuilder.EndSection(); if (GetDefault()->bTextAssetFormatSupport) { MenuBuilder.BeginSection("AssetContextTextAssetFormatActions", LOCTEXT("AssetContextTextAssetFormatActionsHeading", "Text Assets")); { MenuBuilder.AddMenuEntry( LOCTEXT("ExportToTextFormat", "Export to text format"), LOCTEXT("ExportToTextFormatTooltip", "Exports the selected asset(s) to the experimental text asset format"), FSlateIcon(), FUIAction(FExecuteAction::CreateSP(this, &FAssetContextMenu::ExportSelectedAssetsToText)) ); } MenuBuilder.EndSection(); } } void FAssetContextMenu::ExportSelectedAssetsToText() { FString FailedPackage; for (const FAssetData& Asset : SelectedAssets) { UPackage* Package = Asset.GetPackage(); FString Filename = FPackageName::LongPackageNameToFilename(Package->GetPathName(), FPackageName::GetTextAssetPackageExtension()); if (!SavePackageHelper(Package, Filename)) { FailedPackage = Package->GetPathName(); break; } } if (FailedPackage.Len() > 0) { FNotificationInfo Info(LOCTEXT("ExportedTextAssetFailed", "Exported selected asset(s) failed")); Info.ExpireDuration = 3.0f; FSlateNotificationManager::Get().AddNotification(Info); } else { FNotificationInfo Info(LOCTEXT("ExportedTextAssetsSuccessfully", "Exported selected asset(s) successfully")); Info.ExpireDuration = 3.0f; FSlateNotificationManager::Get().AddNotification(Info); } } bool FAssetContextMenu::CanExportSelectedAssetsToText() const { return true; } bool FAssetContextMenu::CanExecuteAssetActions() const { return !bAtLeastOneClassSelected; } void FAssetContextMenu::MakeAssetLocalizationSubMenu(FMenuBuilder& MenuBuilder) { TArray CurrentCultures; // Build up the list of cultures already used { TSet CultureNames; bool bIncludeEngineCultures = false; bool bIncludeProjectCultures = false; for (const FAssetData& Asset : SelectedAssets) { const FString AssetPath = Asset.ObjectPath.ToString(); if (ContentBrowserUtils::IsEngineFolder(AssetPath)) { bIncludeEngineCultures = true; } else { bIncludeProjectCultures = true; } { FString AssetLocalizationRoot; if (FPackageLocalizationUtil::GetLocalizedRoot(AssetPath, FString(), AssetLocalizationRoot)) { FString AssetLocalizationFileRoot; if (FPackageName::TryConvertLongPackageNameToFilename(AssetLocalizationRoot, AssetLocalizationFileRoot)) { TArray CulturePaths; CulturePaths.Add(MoveTemp(AssetLocalizationFileRoot)); CultureNames.Append(TextLocalizationResourceUtil::GetLocalizedCultureNames(CulturePaths)); } } } } ELocalizationLoadFlags LocLoadFlags = ELocalizationLoadFlags::None; if (bIncludeEngineCultures) { LocLoadFlags |= ELocalizationLoadFlags::Engine; } if (bIncludeProjectCultures) { LocLoadFlags |= ELocalizationLoadFlags::Game; } CultureNames.Append(FTextLocalizationManager::Get().GetLocalizedCultureNames(LocLoadFlags)); CurrentCultures = FInternationalization::Get().GetAvailableCultures(CultureNames.Array(), false); if (CurrentCultures.Num() == 0) { CurrentCultures.Add(FInternationalization::Get().GetCurrentCulture()); } } // Sort by display name for the UI CurrentCultures.Sort([](const FCultureRef& FirstCulture, const FCultureRef& SecondCulture) -> bool { const FText FirstDisplayName = FText::FromString(FirstCulture->GetDisplayName()); const FText SecondDisplayName = FText::FromString(SecondCulture->GetDisplayName()); return FirstDisplayName.CompareTo(SecondDisplayName) < 0; }); FAssetToolsModule& AssetToolsModule = FModuleManager::Get().LoadModuleChecked("AssetTools"); // Now build up the list of available localized or source assets based upon the current selection and current cultures FSourceAssetsState SourceAssetsState; TArray LocalizedAssetsState; for (const FCultureRef& CurrentCulture : CurrentCultures) { FLocalizedAssetsState& LocalizedAssetsStateForCulture = LocalizedAssetsState[LocalizedAssetsState.AddDefaulted()]; LocalizedAssetsStateForCulture.Culture = CurrentCulture; for (const FAssetData& Asset : SelectedAssets) { // Can this type of asset be localized? bool bCanLocalizeAsset = false; { TSharedPtr AssetTypeActions = AssetToolsModule.Get().GetAssetTypeActionsForClass(Asset.GetClass()).Pin(); if (AssetTypeActions.IsValid()) { bCanLocalizeAsset = AssetTypeActions->CanLocalize(); } } if (!bCanLocalizeAsset) { continue; } const FString ObjectPath = Asset.ObjectPath.ToString(); if (FPackageName::IsLocalizedPackage(ObjectPath)) { // Get the source path for this asset FString SourceObjectPath; if (FPackageLocalizationUtil::ConvertLocalizedToSource(ObjectPath, SourceObjectPath)) { SourceAssetsState.CurrentAssets.Add(*SourceObjectPath); } } else { SourceAssetsState.SelectedAssets.Add(Asset.ObjectPath); // Get the localized path for this asset and culture FString LocalizedObjectPath; if (FPackageLocalizationUtil::ConvertSourceToLocalized(ObjectPath, CurrentCulture->GetName(), LocalizedObjectPath)) { // Does this localized asset already exist? FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked(TEXT("AssetRegistry")); FAssetData LocalizedAssetData = AssetRegistryModule.Get().GetAssetByObjectPath(*LocalizedObjectPath); if (LocalizedAssetData.IsValid()) { LocalizedAssetsStateForCulture.CurrentAssets.Add(*LocalizedObjectPath); } else { LocalizedAssetsStateForCulture.NewAssets.Add(*LocalizedObjectPath); } } } } } #if USE_STABLE_LOCALIZATION_KEYS // Add the Localization ID options { MenuBuilder.BeginSection(NAME_None, LOCTEXT("LocalizationIdHeading", "Localization ID")); { // Show the localization ID if we have a single asset selected if (SelectedAssets.Num() == 1) { const FString LocalizationId = TextNamespaceUtil::GetPackageNamespace(SelectedAssets[0].GetAsset()); MenuBuilder.AddMenuEntry( FText::Format(LOCTEXT("CopyLocalizationIdFmt", "ID: {0}"), LocalizationId.IsEmpty() ? LOCTEXT("EmptyLocalizationId", "None") : FText::FromString(LocalizationId)), LOCTEXT("CopyLocalizationIdTooltip", "Copy the localization ID to the clipboard."), FSlateIcon(), FUIAction(FExecuteAction::CreateSP(this, &FAssetContextMenu::ExecuteCopyTextToClipboard, LocalizationId)) ); } // Always show the reset localization ID option MenuBuilder.AddMenuEntry( LOCTEXT("ResetLocalizationId", "Reset Localization ID"), LOCTEXT("ResetLocalizationIdTooltip", "Reset the localization ID. Note: This will re-key all the text within this asset."), FSlateIcon(), FUIAction(FExecuteAction::CreateSP(this, &FAssetContextMenu::ExecuteResetLocalizationId)) ); } MenuBuilder.EndSection(); } #endif // USE_STABLE_LOCALIZATION_KEYS // Add the localization cache options if (SelectedAssets.Num() == 1) { FString PackageFilename; if (FPackageName::DoesPackageExist(SelectedAssets[0].PackageName.ToString(), nullptr, &PackageFilename)) { MenuBuilder.BeginSection(NAME_None, LOCTEXT("LocalizationCacheHeading", "Localization Cache")); { // Always show the reset localization ID option MenuBuilder.AddMenuEntry( LOCTEXT("ShowLocalizationCache", "Show Localization Cache"), LOCTEXT("ShowLocalizationCacheTooltip", "Show the cached list of localized texts stored in the package header."), FSlateIcon(), FUIAction(FExecuteAction::CreateSP(this, &FAssetContextMenu::ExecuteShowLocalizationCache, PackageFilename)) ); } MenuBuilder.EndSection(); } } // If we found source assets for localized assets, then we can show the Source Asset options if (SourceAssetsState.CurrentAssets.Num() > 0) { MenuBuilder.BeginSection(NAME_None, LOCTEXT("ManageSourceAssetHeading", "Manage Source Asset")); { MenuBuilder.AddMenuEntry( LOCTEXT("ShowSourceAsset", "Show Source Asset"), LOCTEXT("ShowSourceAssetTooltip", "Show the source asset in the Content Browser."), FSlateIcon(FEditorStyle::GetStyleSetName(), "SystemWideCommands.FindInContentBrowser"), FUIAction(FExecuteAction::CreateSP(this, &FAssetContextMenu::ExecuteFindInAssetTree, SourceAssetsState.CurrentAssets.Array())) ); MenuBuilder.AddMenuEntry( LOCTEXT("EditSourceAsset", "Edit Source Asset"), LOCTEXT("EditSourceAssetTooltip", "Edit the source asset."), FSlateIcon(FEditorStyle::GetStyleSetName(), "ContentBrowser.AssetActions.Edit"), FUIAction(FExecuteAction::CreateSP(this, &FAssetContextMenu::ExecuteOpenEditorsForAssets, SourceAssetsState.CurrentAssets.Array())) ); } MenuBuilder.EndSection(); } // If we currently have source assets selected, then we can show the Localized Asset options if (SourceAssetsState.SelectedAssets.Num() > 0) { MenuBuilder.BeginSection(NAME_None, LOCTEXT("ManageLocalizedAssetHeading", "Manage Localized Asset")); { MenuBuilder.AddSubMenu( LOCTEXT("CreateLocalizedAsset", "Create Localized Asset"), LOCTEXT("CreateLocalizedAssetTooltip", "Create a new localized asset."), FNewMenuDelegate::CreateSP(this, &FAssetContextMenu::MakeCreateLocalizedAssetSubMenu, SourceAssetsState.SelectedAssets, LocalizedAssetsState), FUIAction(), NAME_None, EUserInterfaceActionType::Button, false, FSlateIcon(FEditorStyle::GetStyleSetName(), "ContentBrowser.AssetActions.Duplicate") ); int32 NumLocalizedAssets = 0; for (const FLocalizedAssetsState& LocalizedAssetsStateForCulture : LocalizedAssetsState) { NumLocalizedAssets += LocalizedAssetsStateForCulture.CurrentAssets.Num(); } if (NumLocalizedAssets > 0) { MenuBuilder.AddSubMenu( LOCTEXT("ShowLocalizedAsset", "Show Localized Asset"), LOCTEXT("ShowLocalizedAssetTooltip", "Show the localized asset in the Content Browser."), FNewMenuDelegate::CreateSP(this, &FAssetContextMenu::MakeShowLocalizedAssetSubMenu, LocalizedAssetsState), FUIAction(), NAME_None, EUserInterfaceActionType::Button, false, FSlateIcon(FEditorStyle::GetStyleSetName(), "SystemWideCommands.FindInContentBrowser") ); MenuBuilder.AddSubMenu( LOCTEXT("EditLocalizedAsset", "Edit Localized Asset"), LOCTEXT("EditLocalizedAssetTooltip", "Edit the localized asset."), FNewMenuDelegate::CreateSP(this, &FAssetContextMenu::MakeEditLocalizedAssetSubMenu, LocalizedAssetsState), FUIAction(), NAME_None, EUserInterfaceActionType::Button, false, FSlateIcon(FEditorStyle::GetStyleSetName(), "ContentBrowser.AssetActions.Edit") ); } } MenuBuilder.EndSection(); } } void FAssetContextMenu::MakeCreateLocalizedAssetSubMenu(FMenuBuilder& MenuBuilder, TSet InSelectedSourceAssets, TArray InLocalizedAssetsState) { for (const FLocalizedAssetsState& LocalizedAssetsStateForCulture : InLocalizedAssetsState) { // If we have less localized assets than we have selected source assets, then we'll have some assets to create localized variants of if (LocalizedAssetsStateForCulture.CurrentAssets.Num() < InSelectedSourceAssets.Num()) { MenuBuilder.AddMenuEntry( FText::FromString(LocalizedAssetsStateForCulture.Culture->GetDisplayName()), FText::GetEmpty(), FSlateIcon(), FUIAction(FExecuteAction::CreateSP(this, &FAssetContextMenu::ExecuteCreateLocalizedAsset, InSelectedSourceAssets, LocalizedAssetsStateForCulture)) ); } } } void FAssetContextMenu::MakeShowLocalizedAssetSubMenu(FMenuBuilder& MenuBuilder, TArray InLocalizedAssetsState) { for (const FLocalizedAssetsState& LocalizedAssetsStateForCulture : InLocalizedAssetsState) { if (LocalizedAssetsStateForCulture.CurrentAssets.Num() > 0) { MenuBuilder.AddMenuEntry( FText::FromString(LocalizedAssetsStateForCulture.Culture->GetDisplayName()), FText::GetEmpty(), FSlateIcon(), FUIAction(FExecuteAction::CreateSP(this, &FAssetContextMenu::ExecuteFindInAssetTree, LocalizedAssetsStateForCulture.CurrentAssets.Array())) ); } } } void FAssetContextMenu::MakeEditLocalizedAssetSubMenu(FMenuBuilder& MenuBuilder, TArray InLocalizedAssetsState) { for (const FLocalizedAssetsState& LocalizedAssetsStateForCulture : InLocalizedAssetsState) { if (LocalizedAssetsStateForCulture.CurrentAssets.Num() > 0) { MenuBuilder.AddMenuEntry( FText::FromString(LocalizedAssetsStateForCulture.Culture->GetDisplayName()), FText::GetEmpty(), FSlateIcon(), FUIAction(FExecuteAction::CreateSP(this, &FAssetContextMenu::ExecuteOpenEditorsForAssets, LocalizedAssetsStateForCulture.CurrentAssets.Array())) ); } } } void FAssetContextMenu::ExecuteCreateLocalizedAsset(TSet InSelectedSourceAssets, FLocalizedAssetsState InLocalizedAssetsStateForCulture) { TArray PackagesToSave; TArray NewObjects; for (const FName SourceAssetName : InSelectedSourceAssets) { if (InLocalizedAssetsStateForCulture.CurrentAssets.Contains(SourceAssetName)) { // Asset is already localized continue; } UObject* SourceAssetObject = LoadObject(nullptr, *SourceAssetName.ToString()); if (!SourceAssetObject) { // Source object cannot be loaded continue; } FString LocalizedPackageName; if (!FPackageLocalizationUtil::ConvertSourceToLocalized(SourceAssetObject->GetOutermost()->GetPathName(), InLocalizedAssetsStateForCulture.Culture->GetName(), LocalizedPackageName)) { continue; } ObjectTools::FPackageGroupName NewAssetName; NewAssetName.PackageName = LocalizedPackageName; NewAssetName.ObjectName = SourceAssetObject->GetName(); TSet PackagesNotDuplicated; UObject* NewObject = ObjectTools::DuplicateSingleObject(SourceAssetObject, NewAssetName, PackagesNotDuplicated); if (NewObject) { PackagesToSave.Add(NewObject->GetOutermost()); NewObjects.Add(FAssetData(NewObject)); } } if (PackagesToSave.Num() > 0) { FEditorFileUtils::PromptForCheckoutAndSave(PackagesToSave, /*bCheckDirty*/false, /*bPromptToSave*/false); } OnFindInAssetTreeRequested.ExecuteIfBound(NewObjects); } void FAssetContextMenu::ExecuteFindInAssetTree(TArray InAssets) { FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked(TEXT("AssetRegistry")); FARFilter ARFilter; ARFilter.ObjectPaths = MoveTemp(InAssets); TArray FoundLocalizedAssetData; AssetRegistryModule.Get().GetAssets(ARFilter, FoundLocalizedAssetData); OnFindInAssetTreeRequested.ExecuteIfBound(FoundLocalizedAssetData); } void FAssetContextMenu::ExecuteOpenEditorsForAssets(TArray InAssets) { FAssetEditorManager::Get().OpenEditorsForAssets(InAssets); } bool FAssetContextMenu::AddReferenceMenuOptions(FMenuBuilder& MenuBuilder) { MenuBuilder.BeginSection("AssetContextReferences", LOCTEXT("ReferencesMenuHeading", "References")); { MenuBuilder.AddMenuEntry( LOCTEXT("CopyReference", "Copy Reference"), LOCTEXT("CopyReferenceTooltip", "Copies reference paths for the selected assets to the clipboard."), FSlateIcon(), FUIAction( FExecuteAction::CreateSP( this, &FAssetContextMenu::ExecuteCopyReference ) ) ); } MenuBuilder.EndSection(); return true; } bool FAssetContextMenu::AddDocumentationMenuOptions(FMenuBuilder& MenuBuilder) { bool bAddedOption = false; // Objects must be loaded for this operation... for now UClass* SelectedClass = (SelectedAssets.Num() > 0 ? SelectedAssets[0].GetClass() : nullptr); for (const FAssetData& AssetData : SelectedAssets) { if (SelectedClass != AssetData.GetClass()) { SelectedClass = nullptr; break; } } // Go to C++ Code if( SelectedClass != nullptr ) { // Blueprints are special. We won't link to C++ and for documentation we'll use the class it is generated from const bool bIsBlueprint = SelectedClass->IsChildOf(); if (bIsBlueprint) { const FString ParentClassPath = SelectedAssets[0].GetTagValueRef(GET_MEMBER_NAME_CHECKED(UBlueprint,ParentClass)); if (!ParentClassPath.IsEmpty()) { SelectedClass = FindObject(nullptr,*ParentClassPath); } } if ( !bIsBlueprint && FSourceCodeNavigation::IsCompilerAvailable() ) { FString ClassHeaderPath; if( FSourceCodeNavigation::FindClassHeaderPath( SelectedClass, ClassHeaderPath ) && IFileManager::Get().FileSize( *ClassHeaderPath ) != INDEX_NONE ) { bAddedOption = true; const FString CodeFileName = FPaths::GetCleanFilename( *ClassHeaderPath ); MenuBuilder.BeginSection( "AssetCode"/*, LOCTEXT("AssetCodeHeading", "C++")*/ ); { MenuBuilder.AddMenuEntry( FText::Format( LOCTEXT("GoToCodeForAsset", "Open {0}"), FText::FromString( CodeFileName ) ), FText::Format( LOCTEXT("GoToCodeForAsset_ToolTip", "Opens the header file for this asset ({0}) in a code editing program"), FText::FromString( CodeFileName ) ), FSlateIcon(FEditorStyle::GetStyleSetName(), "ContentBrowser.AssetActions.GoToCodeForAsset"), FUIAction( FExecuteAction::CreateSP( this, &FAssetContextMenu::ExecuteGoToCodeForAsset, SelectedClass ) ) ); } MenuBuilder.EndSection(); } } const FString DocumentationLink = FEditorClassUtils::GetDocumentationLink(SelectedClass); if (bIsBlueprint || !DocumentationLink.IsEmpty()) { bAddedOption = true; MenuBuilder.BeginSection( "AssetDocumentation"/*, LOCTEXT("AseetDocsHeading", "Documentation")*/ ); { if (bIsBlueprint) { if (!DocumentationLink.IsEmpty()) { MenuBuilder.AddMenuEntry( FText::Format( LOCTEXT("GoToDocsForAssetWithClass", "View Documentation - {0}"), SelectedClass->GetDisplayNameText() ), FText::Format( LOCTEXT("GoToDocsForAssetWithClass_ToolTip", "Click to open documentation for {0}"), SelectedClass->GetDisplayNameText() ), FSlateIcon(FEditorStyle::GetStyleSetName(), "HelpIcon.Hovered" ), FUIAction( FExecuteAction::CreateSP( this, &FAssetContextMenu::ExecuteGoToDocsForAsset, SelectedClass ) ) ); } UEnum* BlueprintTypeEnum = StaticEnum(); const FString EnumString = SelectedAssets[0].GetTagValueRef(GET_MEMBER_NAME_CHECKED(UBlueprint,BlueprintType)); EBlueprintType BlueprintType = (!EnumString.IsEmpty() ? (EBlueprintType)BlueprintTypeEnum->GetValueByName(*EnumString) : BPTYPE_Normal); switch (BlueprintType) { case BPTYPE_FunctionLibrary: MenuBuilder.AddMenuEntry( LOCTEXT("GoToDocsForMacroBlueprint", "View Documentation - Function Library"), LOCTEXT("GoToDocsForMacroBlueprint_ToolTip", "Click to open documentation on blueprint function libraries"), FSlateIcon(FEditorStyle::GetStyleSetName(), "HelpIcon.Hovered" ), FUIAction( FExecuteAction::CreateSP( this, &FAssetContextMenu::ExecuteGoToDocsForAsset, UBlueprint::StaticClass(), FString(TEXT("UBlueprint_FunctionLibrary")) ) ) ); break; case BPTYPE_Interface: MenuBuilder.AddMenuEntry( LOCTEXT("GoToDocsForInterfaceBlueprint", "View Documentation - Interface"), LOCTEXT("GoToDocsForInterfaceBlueprint_ToolTip", "Click to open documentation on blueprint interfaces"), FSlateIcon(FEditorStyle::GetStyleSetName(), "HelpIcon.Hovered" ), FUIAction( FExecuteAction::CreateSP( this, &FAssetContextMenu::ExecuteGoToDocsForAsset, UBlueprint::StaticClass(), FString(TEXT("UBlueprint_Interface")) ) ) ); break; case BPTYPE_MacroLibrary: MenuBuilder.AddMenuEntry( LOCTEXT("GoToDocsForMacroLibrary", "View Documentation - Macro"), LOCTEXT("GoToDocsForMacroLibrary_ToolTip", "Click to open documentation on blueprint macros"), FSlateIcon(FEditorStyle::GetStyleSetName(), "HelpIcon.Hovered" ), FUIAction( FExecuteAction::CreateSP( this, &FAssetContextMenu::ExecuteGoToDocsForAsset, UBlueprint::StaticClass(), FString(TEXT("UBlueprint_Macro")) ) ) ); break; default: MenuBuilder.AddMenuEntry( LOCTEXT("GoToDocsForBlueprint", "View Documentation - Blueprint"), LOCTEXT("GoToDocsForBlueprint_ToolTip", "Click to open documentation on blueprints"), FSlateIcon(FEditorStyle::GetStyleSetName(), "HelpIcon.Hovered" ), FUIAction( FExecuteAction::CreateSP( this, &FAssetContextMenu::ExecuteGoToDocsForAsset, UBlueprint::StaticClass(), FString(TEXT("UBlueprint")) ) ) ); } } else { MenuBuilder.AddMenuEntry( LOCTEXT("GoToDocsForAsset", "View Documentation"), LOCTEXT("GoToDocsForAsset_ToolTip", "Click to open documentation"), FSlateIcon(FEditorStyle::GetStyleSetName(), "HelpIcon.Hovered" ), FUIAction( FExecuteAction::CreateSP( this, &FAssetContextMenu::ExecuteGoToDocsForAsset, SelectedClass ) ) ); } } MenuBuilder.EndSection(); } } return bAddedOption; } bool FAssetContextMenu::AddAssetTypeMenuOptions(FMenuBuilder& MenuBuilder) { bool bAnyTypeOptions = false; // Objects must be loaded for this operation... for now TArray ObjectPaths; for (int32 AssetIdx = 0; AssetIdx < SelectedAssets.Num(); ++AssetIdx) { ObjectPaths.Add(SelectedAssets[AssetIdx].ObjectPath.ToString()); } TArray SelectedObjects; if ( ContentBrowserUtils::LoadAssetsIfNeeded(ObjectPaths, SelectedObjects) ) { // Load the asset tools module FAssetToolsModule& AssetToolsModule = FModuleManager::LoadModuleChecked(TEXT("AssetTools")); bAnyTypeOptions = AssetToolsModule.Get().GetAssetActions(SelectedObjects, MenuBuilder, /*bIncludeHeading=*/true); } return bAnyTypeOptions; } bool FAssetContextMenu::AddSourceControlMenuOptions(FMenuBuilder& MenuBuilder) { MenuBuilder.BeginSection("AssetContextSourceControl"); if ( ISourceControlModule::Get().IsEnabled() ) { // SCC sub menu MenuBuilder.AddSubMenu( LOCTEXT("SourceControlSubMenuLabel", "Source Control"), LOCTEXT("SourceControlSubMenuToolTip", "Source control actions."), FNewMenuDelegate::CreateSP(this, &FAssetContextMenu::FillSourceControlSubMenu), FUIAction( FExecuteAction(), FCanExecuteAction::CreateSP( this, &FAssetContextMenu::CanExecuteSourceControlActions ) ), NAME_None, EUserInterfaceActionType::Button, false, FSlateIcon(FEditorStyle::GetStyleSetName(), "SourceControl.StatusIcon.On") ); } else { MenuBuilder.AddMenuEntry( LOCTEXT("SCCConnectToSourceControl", "Connect To Source Control..."), LOCTEXT("SCCConnectToSourceControlTooltip", "Connect to source control to allow source control operations to be performed on content and levels."), FSlateIcon(FEditorStyle::GetStyleSetName(), "SourceControl.Actions.Connect"), FUIAction( FExecuteAction::CreateSP( this, &FAssetContextMenu::ExecuteEnableSourceControl ), FCanExecuteAction::CreateSP( this, &FAssetContextMenu::CanExecuteSourceControlActions ) ) ); } // Diff selected if (CanExecuteDiffSelected()) { MenuBuilder.AddMenuEntry( LOCTEXT("DiffSelected", "Diff Selected"), LOCTEXT("DiffSelectedTooltip", "Diff the two assets that you have selected."), FSlateIcon(FEditorStyle::GetStyleSetName(), "SourceControl.Actions.Diff"), FUIAction( FExecuteAction::CreateSP(this, &FAssetContextMenu::ExecuteDiffSelected) ) ); } MenuBuilder.EndSection(); return true; } void FAssetContextMenu::FillSourceControlSubMenu(FMenuBuilder& MenuBuilder) { MenuBuilder.BeginSection("AssetSourceControlActions", LOCTEXT("AssetSourceControlActionsMenuHeading", "Source Control")); if( CanExecuteSCCMerge() ) { MenuBuilder.AddMenuEntry( LOCTEXT("SCCMerge", "Merge"), LOCTEXT("SCCMergeTooltip", "Opens the blueprint editor with the merge tool open."), FSlateIcon(), FUIAction( FExecuteAction::CreateSP(this, &FAssetContextMenu::ExecuteSCCMerge), FCanExecuteAction::CreateSP(this, &FAssetContextMenu::CanExecuteSCCMerge) ) ); } if( CanExecuteSCCSync() ) { MenuBuilder.AddMenuEntry( LOCTEXT("SCCSync", "Sync"), LOCTEXT("SCCSyncTooltip", "Updates the item to the latest version in source control."), FSlateIcon(FEditorStyle::GetStyleSetName(), "SourceControl.Actions.Sync"), FUIAction( FExecuteAction::CreateSP( this, &FAssetContextMenu::ExecuteSCCSync ), FCanExecuteAction::CreateSP( this, &FAssetContextMenu::CanExecuteSCCSync ) ) ); } if ( CanExecuteSCCCheckOut() ) { MenuBuilder.AddMenuEntry( LOCTEXT("SCCCheckOut", "Check Out"), LOCTEXT("SCCCheckOutTooltip", "Checks out the selected asset from source control."), FSlateIcon(FEditorStyle::GetStyleSetName(), "SourceControl.Actions.CheckOut"), FUIAction( FExecuteAction::CreateSP( this, &FAssetContextMenu::ExecuteSCCCheckOut ), FCanExecuteAction::CreateSP( this, &FAssetContextMenu::CanExecuteSCCCheckOut ) ) ); } if ( CanExecuteSCCOpenForAdd() ) { MenuBuilder.AddMenuEntry( LOCTEXT("SCCOpenForAdd", "Mark For Add"), LOCTEXT("SCCOpenForAddTooltip", "Adds the selected asset to source control."), FSlateIcon(FEditorStyle::GetStyleSetName(), "SourceControl.Actions.Add"), FUIAction( FExecuteAction::CreateSP( this, &FAssetContextMenu::ExecuteSCCOpenForAdd ), FCanExecuteAction::CreateSP( this, &FAssetContextMenu::CanExecuteSCCOpenForAdd ) ) ); } if ( CanExecuteSCCCheckIn() ) { MenuBuilder.AddMenuEntry( LOCTEXT("SCCCheckIn", "Check In"), LOCTEXT("SCCCheckInTooltip", "Checks in the selected asset to source control."), FSlateIcon(FEditorStyle::GetStyleSetName(), "SourceControl.Actions.Submit"), FUIAction( FExecuteAction::CreateSP( this, &FAssetContextMenu::ExecuteSCCCheckIn ), FCanExecuteAction::CreateSP( this, &FAssetContextMenu::CanExecuteSCCCheckIn ) ) ); } MenuBuilder.AddMenuEntry( LOCTEXT("SCCRefresh", "Refresh"), LOCTEXT("SCCRefreshTooltip", "Updates the source control status of the asset."), FSlateIcon(FEditorStyle::GetStyleSetName(), "SourceControl.Actions.Refresh"), FUIAction( FExecuteAction::CreateSP( this, &FAssetContextMenu::ExecuteSCCRefresh ), FCanExecuteAction::CreateSP( this, &FAssetContextMenu::CanExecuteSCCRefresh ) ) ); if( CanExecuteSCCHistory() ) { MenuBuilder.AddMenuEntry( LOCTEXT("SCCHistory", "History"), LOCTEXT("SCCHistoryTooltip", "Displays the source control revision history of the selected asset."), FSlateIcon(FEditorStyle::GetStyleSetName(), "SourceControl.Actions.History"), FUIAction( FExecuteAction::CreateSP( this, &FAssetContextMenu::ExecuteSCCHistory ), FCanExecuteAction::CreateSP( this, &FAssetContextMenu::CanExecuteSCCHistory ) ) ); MenuBuilder.AddMenuEntry( LOCTEXT("SCCDiffAgainstDepot", "Diff Against Depot"), LOCTEXT("SCCDiffAgainstDepotTooltip", "Look at differences between your version of the asset and that in source control."), FSlateIcon(FEditorStyle::GetStyleSetName(), "SourceControl.Actions.Diff"), FUIAction( FExecuteAction::CreateSP( this, &FAssetContextMenu::ExecuteSCCDiffAgainstDepot ), FCanExecuteAction::CreateSP( this, &FAssetContextMenu::CanExecuteSCCDiffAgainstDepot ) ) ); } if( CanExecuteSCCRevert() ) { MenuBuilder.AddMenuEntry( LOCTEXT("SCCRevert", "Revert"), LOCTEXT("SCCRevertTooltip", "Reverts the asset to the state it was before it was checked out."), FSlateIcon(FEditorStyle::GetStyleSetName(), "SourceControl.Actions.Revert"), FUIAction( FExecuteAction::CreateSP( this, &FAssetContextMenu::ExecuteSCCRevert ), FCanExecuteAction::CreateSP( this, &FAssetContextMenu::CanExecuteSCCRevert ) ) ); } MenuBuilder.EndSection(); } bool FAssetContextMenu::CanExecuteSourceControlActions() const { return !bAtLeastOneClassSelected; } bool FAssetContextMenu::AddCollectionMenuOptions(FMenuBuilder& MenuBuilder) { class FManageCollectionsContextMenu { public: static void CreateManageCollectionsSubMenu(FMenuBuilder& SubMenuBuilder, TSharedRef QuickAssetManagement) { FCollectionManagerModule& CollectionManagerModule = FCollectionManagerModule::GetModule(); TArray AvailableCollections; CollectionManagerModule.Get().GetRootCollections(AvailableCollections); CreateManageCollectionsSubMenu(SubMenuBuilder, QuickAssetManagement, MoveTemp(AvailableCollections)); } static void CreateManageCollectionsSubMenu(FMenuBuilder& SubMenuBuilder, TSharedRef QuickAssetManagement, TArray AvailableCollections) { FCollectionManagerModule& CollectionManagerModule = FCollectionManagerModule::GetModule(); AvailableCollections.Sort([](const FCollectionNameType& One, const FCollectionNameType& Two) -> bool { return One.Name < Two.Name; }); for (const FCollectionNameType& AvailableCollection : AvailableCollections) { // Never display system collections if (AvailableCollection.Type == ECollectionShareType::CST_System) { continue; } // Can only manage assets for static collections ECollectionStorageMode::Type StorageMode = ECollectionStorageMode::Static; CollectionManagerModule.Get().GetCollectionStorageMode(AvailableCollection.Name, AvailableCollection.Type, StorageMode); if (StorageMode != ECollectionStorageMode::Static) { continue; } TArray AvailableChildCollections; CollectionManagerModule.Get().GetChildCollections(AvailableCollection.Name, AvailableCollection.Type, AvailableChildCollections); if (AvailableChildCollections.Num() > 0) { SubMenuBuilder.AddSubMenu( FText::FromName(AvailableCollection.Name), FText::GetEmpty(), FNewMenuDelegate::CreateStatic(&FManageCollectionsContextMenu::CreateManageCollectionsSubMenu, QuickAssetManagement, AvailableChildCollections), FUIAction( FExecuteAction::CreateStatic(&FManageCollectionsContextMenu::OnCollectionClicked, QuickAssetManagement, AvailableCollection), FCanExecuteAction::CreateStatic(&FManageCollectionsContextMenu::IsCollectionEnabled, QuickAssetManagement, AvailableCollection), FGetActionCheckState::CreateStatic(&FManageCollectionsContextMenu::GetCollectionCheckState, QuickAssetManagement, AvailableCollection) ), NAME_None, EUserInterfaceActionType::ToggleButton, false, FSlateIcon(FEditorStyle::GetStyleSetName(), ECollectionShareType::GetIconStyleName(AvailableCollection.Type)) ); } else { SubMenuBuilder.AddMenuEntry( FText::FromName(AvailableCollection.Name), FText::GetEmpty(), FSlateIcon(FEditorStyle::GetStyleSetName(), ECollectionShareType::GetIconStyleName(AvailableCollection.Type)), FUIAction( FExecuteAction::CreateStatic(&FManageCollectionsContextMenu::OnCollectionClicked, QuickAssetManagement, AvailableCollection), FCanExecuteAction::CreateStatic(&FManageCollectionsContextMenu::IsCollectionEnabled, QuickAssetManagement, AvailableCollection), FGetActionCheckState::CreateStatic(&FManageCollectionsContextMenu::GetCollectionCheckState, QuickAssetManagement, AvailableCollection) ), NAME_None, EUserInterfaceActionType::ToggleButton ); } } } private: static bool IsCollectionEnabled(TSharedRef QuickAssetManagement, FCollectionNameType InCollectionKey) { return QuickAssetManagement->IsCollectionEnabled(InCollectionKey); } static ECheckBoxState GetCollectionCheckState(TSharedRef QuickAssetManagement, FCollectionNameType InCollectionKey) { return QuickAssetManagement->GetCollectionCheckState(InCollectionKey); } static void OnCollectionClicked(TSharedRef QuickAssetManagement, FCollectionNameType InCollectionKey) { // The UI actions don't give you the new check state, so we need to emulate the behavior of SCheckBox // Basically, checked will transition to unchecked (removing items), and anything else will transition to checked (adding items) if (GetCollectionCheckState(QuickAssetManagement, InCollectionKey) == ECheckBoxState::Checked) { QuickAssetManagement->RemoveCurrentAssetsFromCollection(InCollectionKey); } else { QuickAssetManagement->AddCurrentAssetsToCollection(InCollectionKey); } } }; bool bHasAddedItems = false; FCollectionManagerModule& CollectionManagerModule = FCollectionManagerModule::GetModule(); MenuBuilder.BeginSection("AssetContextCollections", LOCTEXT("AssetCollectionOptionsMenuHeading", "Collections")); // Show a sub-menu that allows you to quickly add or remove the current asset selection from the available collections if (CollectionManagerModule.Get().HasCollections()) { TSharedRef QuickAssetManagement = MakeShareable(new FCollectionAssetManagement()); QuickAssetManagement->SetCurrentAssets(SelectedAssets); MenuBuilder.AddSubMenu( LOCTEXT("ManageCollections", "Manage Collections"), LOCTEXT("ManageCollections_ToolTip", "Manage the collections that the selected asset(s) belong to."), FNewMenuDelegate::CreateStatic(&FManageCollectionsContextMenu::CreateManageCollectionsSubMenu, QuickAssetManagement) ); bHasAddedItems = true; } // "Remove from collection" (only display option if exactly one collection is selected) if ( SourcesData.Collections.Num() == 1 && !SourcesData.IsDynamicCollection() ) { MenuBuilder.AddMenuEntry( FText::Format(LOCTEXT("RemoveFromCollectionFmt", "Remove From {0}"), FText::FromName(SourcesData.Collections[0].Name)), LOCTEXT("RemoveFromCollection_ToolTip", "Removes the selected asset from the current collection."), FSlateIcon(), FUIAction( FExecuteAction::CreateSP( this, &FAssetContextMenu::ExecuteRemoveFromCollection ), FCanExecuteAction::CreateSP( this, &FAssetContextMenu::CanExecuteRemoveFromCollection ) ) ); bHasAddedItems = true; } MenuBuilder.EndSection(); return bHasAddedItems; } bool FAssetContextMenu::AreImportedAssetActionsVisible() const { FAssetToolsModule& AssetToolsModule = FModuleManager::Get().LoadModuleChecked("AssetTools"); // Check that all of the selected assets are imported for (auto& SelectedAsset : SelectedAssets) { auto AssetClass = SelectedAsset.GetClass(); if (AssetClass) { auto AssetTypeActions = AssetToolsModule.Get().GetAssetTypeActionsForClass(AssetClass).Pin(); if (!AssetTypeActions.IsValid() || !AssetTypeActions->IsImportedAsset()) { return false; } } } return true; } bool FAssetContextMenu::CanExecuteImportedAssetActions(const TArray ResolvedFilePaths) const { // Verify that all the file paths are legitimate for (const auto& SourceFilePath : ResolvedFilePaths) { if (!SourceFilePath.Len() || IFileManager::Get().FileSize(*SourceFilePath) == INDEX_NONE) { return false; } } return true; } void FAssetContextMenu::ExecuteReimport(int32 SourceFileIndex /*= INDEX_NONE*/) { // Reimport all selected assets TArray CopyOfSelectedAssets; for (const FAssetData &SelectedAsset : SelectedAssets) { UObject *Asset = SelectedAsset.GetAsset(); CopyOfSelectedAssets.Add(Asset); } FReimportManager::Instance()->ValidateAllSourceFileAndReimport(CopyOfSelectedAssets, true, SourceFileIndex, false); } void FAssetContextMenu::ExecuteReimportWithNewFile(int32 SourceFileIndex /*= INDEX_NONE*/) { // Ask for a new files and reimport the selected asset check(SelectedAssets.Num() == 1); TArray CopyOfSelectedAssets; for (const FAssetData &SelectedAsset : SelectedAssets) { UObject *Asset = SelectedAsset.GetAsset(); CopyOfSelectedAssets.Add(Asset); } TArray AssetSourcePaths; UClass* ObjectClass = CopyOfSelectedAssets[0]->GetClass(); FAssetToolsModule& AssetToolsModule = FModuleManager::Get().LoadModuleChecked("AssetTools"); const auto AssetTypeActions = AssetToolsModule.Get().GetAssetTypeActionsForClass(ObjectClass); if (AssetTypeActions.IsValid()) { AssetTypeActions.Pin()->GetResolvedSourceFilePaths(CopyOfSelectedAssets, AssetSourcePaths); } int32 SourceFileIndexToReplace = SourceFileIndex; //Check if the data is valid if (SourceFileIndex == INDEX_NONE) { check(AssetSourcePaths.Num() <= 1); //Ask for a new file for the index 0 SourceFileIndexToReplace = 0; } else { check(AssetSourcePaths.IsValidIndex(SourceFileIndex)); } check(SourceFileIndexToReplace >= 0); FReimportManager::Instance()->ValidateAllSourceFileAndReimport(CopyOfSelectedAssets, true, SourceFileIndexToReplace, true); } void FAssetContextMenu::ExecuteFindSourceInExplorer(const TArray ResolvedFilePaths) { // Open all files in the explorer for (const auto& SourceFilePath : ResolvedFilePaths) { FPlatformProcess::ExploreFolder(*FPaths::GetPath(SourceFilePath)); } } void FAssetContextMenu::ExecuteOpenInExternalEditor(const TArray ResolvedFilePaths) { // Open all files in their respective editor for (const auto& SourceFilePath : ResolvedFilePaths) { FPlatformProcess::LaunchFileInDefaultExternalApplication(*SourceFilePath, NULL, ELaunchVerb::Edit); } } void FAssetContextMenu::GetSelectedAssetsByClass(TMap >& OutSelectedAssetsByClass) const { // Sort all selected assets by class for (const auto& SelectedAsset : SelectedAssets) { auto Asset = SelectedAsset.GetAsset(); auto AssetClass = Asset->GetClass(); if ( !OutSelectedAssetsByClass.Contains(AssetClass) ) { OutSelectedAssetsByClass.Add(AssetClass); } OutSelectedAssetsByClass[AssetClass].Add(Asset); } } void FAssetContextMenu::GetSelectedAssetSourceFilePaths(TArray& OutFilePaths, TArray& OutUniqueSourceFileLabels, int32 &OutValidSelectedAssetCount) const { OutFilePaths.Empty(); OutUniqueSourceFileLabels.Empty(); TMap > SelectedAssetsByClass; GetSelectedAssetsByClass(SelectedAssetsByClass); FAssetToolsModule& AssetToolsModule = FModuleManager::Get().LoadModuleChecked("AssetTools"); OutValidSelectedAssetCount = 0; // Get the source file paths for the assets of each type for (const auto& AssetsByClassPair : SelectedAssetsByClass) { const auto AssetTypeActions = AssetToolsModule.Get().GetAssetTypeActionsForClass(AssetsByClassPair.Key); if (AssetTypeActions.IsValid()) { const auto& TypeAssets = AssetsByClassPair.Value; OutValidSelectedAssetCount += TypeAssets.Num(); TArray AssetSourcePaths; AssetTypeActions.Pin()->GetResolvedSourceFilePaths(TypeAssets, AssetSourcePaths); OutFilePaths.Append(AssetSourcePaths); TArray AssetSourceLabels; AssetTypeActions.Pin()->GetSourceFileLabels(TypeAssets, AssetSourceLabels); for (const FString& Label : AssetSourceLabels) { OutUniqueSourceFileLabels.AddUnique(Label); } } } } void FAssetContextMenu::ExecuteSyncToAssetTree() { // Copy this as the sync may adjust our selected assets array const TArray SelectedAssetsCopy = SelectedAssets; OnFindInAssetTreeRequested.ExecuteIfBound(SelectedAssetsCopy); } void FAssetContextMenu::ExecuteFindInExplorer() { for (int32 AssetIdx = 0; AssetIdx < SelectedAssets.Num(); ++AssetIdx) { const UObject* Asset = SelectedAssets[AssetIdx].GetAsset(); if (Asset) { FAssetData AssetData(Asset); const FString PackageName = AssetData.PackageName.ToString(); static const TCHAR* ScriptString = TEXT("/Script/"); if (PackageName.StartsWith(ScriptString)) { // Handle C++ classes specially, as FPackageName::LongPackageNameToFilename won't return the correct path in this case const FString ModuleName = PackageName.RightChop(FCString::Strlen(ScriptString)); FString ModulePath; if (FSourceCodeNavigation::FindModulePath(ModuleName, ModulePath)) { FString RelativePath; if (AssetData.GetTagValue("ModuleRelativePath", RelativePath)) { const FString FullFilePath = FPaths::ConvertRelativePathToFull(ModulePath / (*RelativePath)); FPlatformProcess::ExploreFolder(*FullFilePath); } } return; } const bool bIsWorldAsset = (AssetData.AssetClass == UWorld::StaticClass()->GetFName()); const FString Extension = bIsWorldAsset ? FPackageName::GetMapPackageExtension() : FPackageName::GetAssetPackageExtension(); const FString FilePath = FPackageName::LongPackageNameToFilename(PackageName, Extension); const FString FullFilePath = FPaths::ConvertRelativePathToFull(FilePath); FPlatformProcess::ExploreFolder(*FullFilePath); } } } void FAssetContextMenu::ExecuteCreateBlueprintUsing() { if(SelectedAssets.Num() == 1) { UObject* Asset = SelectedAssets[0].GetAsset(); FKismetEditorUtilities::CreateBlueprintUsingAsset(Asset, true); } } void FAssetContextMenu::GetSelectedAssets(TArray& Assets, bool SkipRedirectors) const { for (int32 AssetIdx = 0; AssetIdx < SelectedAssets.Num(); ++AssetIdx) { if (SkipRedirectors && (SelectedAssets[AssetIdx].AssetClass == UObjectRedirector::StaticClass()->GetFName())) { // Don't operate on Redirectors continue; } UObject* Object = SelectedAssets[AssetIdx].GetAsset(); if (Object) { Assets.Add(Object); } } } /** Generates a reference graph of the world and can then find actors referencing specified objects */ struct WorldReferenceGenerator : public FFindReferencedAssets { void BuildReferencingData() { MarkAllObjects(); const int32 MaxRecursionDepth = 0; const bool bIncludeClasses = true; const bool bIncludeDefaults = false; const bool bReverseReferenceGraph = true; UWorld* World = GWorld; // Generate the reference graph for the world FReferencedAssets* WorldReferencer = new(Referencers)FReferencedAssets(World); FFindAssetsArchive(World, WorldReferencer->AssetList, &ReferenceGraph, MaxRecursionDepth, bIncludeClasses, bIncludeDefaults, bReverseReferenceGraph); // Also include all the streaming levels in the results for (ULevelStreaming* StreamingLevel : World->GetStreamingLevels()) { if (StreamingLevel) { if (ULevel* Level = StreamingLevel->GetLoadedLevel()) { // Generate the reference graph for each streamed in level FReferencedAssets* LevelReferencer = new(Referencers) FReferencedAssets(Level); FFindAssetsArchive(Level, LevelReferencer->AssetList, &ReferenceGraph, MaxRecursionDepth, bIncludeClasses, bIncludeDefaults, bReverseReferenceGraph); } } } TArray ReferencedObjects; // Special case for blueprints for (AActor* Actor : FActorRange(World)) { ReferencedObjects.Reset(); Actor->GetReferencedContentObjects(ReferencedObjects); for(UObject* Reference : ReferencedObjects) { TSet& Objects = ReferenceGraph.FindOrAdd(Reference); Objects.Add(Actor); } } } void MarkAllObjects() { // Mark all objects so we don't get into an endless recursion for (FObjectIterator It; It; ++It) { It->Mark(OBJECTMARK_TagExp); } } void Generate( const UObject* AssetToFind, TSet& OutObjects ) { // Don't examine visited objects if (!AssetToFind->HasAnyMarks(OBJECTMARK_TagExp)) { return; } AssetToFind->UnMark(OBJECTMARK_TagExp); // Return once we find a parent object that is an actor if (AssetToFind->IsA(AActor::StaticClass())) { OutObjects.Add(AssetToFind); return; } // Traverse the reference graph looking for actor objects TSet* ReferencingObjects = ReferenceGraph.Find(AssetToFind); if (ReferencingObjects) { for(TSet::TConstIterator SetIt(*ReferencingObjects); SetIt; ++SetIt) { Generate(*SetIt, OutObjects); } } } }; void FAssetContextMenu::ExecuteFindAssetInWorld() { TArray AssetsToFind; const bool SkipRedirectors = true; GetSelectedAssets(AssetsToFind, SkipRedirectors); const bool NoteSelectionChange = true; const bool DeselectBSPSurfs = true; const bool WarnAboutManyActors = false; GEditor->SelectNone(NoteSelectionChange, DeselectBSPSurfs, WarnAboutManyActors); if (AssetsToFind.Num() > 0) { FScopedSlowTask SlowTask(2 + AssetsToFind.Num(), NSLOCTEXT("AssetContextMenu", "FindAssetInWorld", "Finding actors that use this asset...")); SlowTask.MakeDialog(); CollectGarbage(GARBAGE_COLLECTION_KEEPFLAGS); TSet OutObjects; WorldReferenceGenerator ObjRefGenerator; SlowTask.EnterProgressFrame(); ObjRefGenerator.BuildReferencingData(); for (UObject* AssetToFind : AssetsToFind) { SlowTask.EnterProgressFrame(); ObjRefGenerator.MarkAllObjects(); ObjRefGenerator.Generate(AssetToFind, OutObjects); } SlowTask.EnterProgressFrame(); if (OutObjects.Num() > 0) { const bool InSelected = true; const bool Notify = false; // Select referencing actors for (const UObject* Object : OutObjects) { GEditor->SelectActor(const_cast(CastChecked(Object)), InSelected, Notify); } GEditor->NoteSelectionChange(); } else { FNotificationInfo Info(LOCTEXT("NoReferencingActorsFound", "No actors found.")); Info.ExpireDuration = 3.0f; FSlateNotificationManager::Get().AddNotification(Info); } } } void FAssetContextMenu::ExecutePropertyMatrix() { TArray ObjectsForPropertiesMenu; const bool SkipRedirectors = true; GetSelectedAssets(ObjectsForPropertiesMenu, SkipRedirectors); if ( ObjectsForPropertiesMenu.Num() > 0 ) { FPropertyEditorModule& PropertyEditorModule = FModuleManager::LoadModuleChecked( "PropertyEditor" ); PropertyEditorModule.CreatePropertyEditorToolkit( EToolkitMode::Standalone, TSharedPtr(), ObjectsForPropertiesMenu ); } } void FAssetContextMenu::ExecuteShowAssetMetaData() { for (const FAssetData& AssetData : SelectedAssets) { UObject* Asset = AssetData.GetAsset(); if (Asset) { TMap* TagValues = UMetaData::GetMapForObject(Asset); if (TagValues) { // Create and display a resizable window to display the MetaDataView for each asset with metadata FString Title = FString::Printf(TEXT("Metadata: %s"), *AssetData.AssetName.ToString()); TSharedPtr< SWindow > Window = SNew(SWindow) .Title(FText::FromString(Title)) .SupportsMaximize(false) .SupportsMinimize(false) .MinWidth(500.0f) .MinHeight(250.0f) [ SNew(SBorder) .Padding(4.f) .BorderImage(FEditorStyle::GetBrush("ToolPanel.GroupBorder")) [ SNew(SMetaDataView, *TagValues) ] ]; FSlateApplication::Get().AddWindow(Window.ToSharedRef()); } } } } void FAssetContextMenu::ExecuteEditAsset() { TMap > SelectedAssetsByClass; GetSelectedAssetsByClass(SelectedAssetsByClass); // Open for (const auto& AssetsByClassPair : SelectedAssetsByClass) { const auto& TypeAssets = AssetsByClassPair.Value; FAssetEditorManager::Get().OpenEditorForAssets(TypeAssets); } } void FAssetContextMenu::ExecuteSaveAsset() { TArray PackagesToSave; GetSelectedPackages(PackagesToSave); const bool bCheckDirty = false; const bool bPromptToSave = false; FEditorFileUtils::PromptForCheckoutAndSave(PackagesToSave, bCheckDirty, bPromptToSave); } void FAssetContextMenu::ExecuteDiffSelected() const { if (SelectedAssets.Num() >= 2) { UObject* FirstObjectSelected = SelectedAssets[0].GetAsset(); UObject* SecondObjectSelected = SelectedAssets[1].GetAsset(); if ((FirstObjectSelected != NULL) && (SecondObjectSelected != NULL)) { // Load the asset registry module FAssetToolsModule& AssetToolsModule = FModuleManager::GetModuleChecked("AssetTools"); FRevisionInfo CurrentRevision; CurrentRevision.Revision = TEXT(""); AssetToolsModule.Get().DiffAssets(FirstObjectSelected, SecondObjectSelected, CurrentRevision, CurrentRevision); } } } void FAssetContextMenu::ExecuteDuplicate() { TArray ObjectsToDuplicate; const bool SkipRedirectors = true; GetSelectedAssets(ObjectsToDuplicate, SkipRedirectors); if ( ObjectsToDuplicate.Num() == 1 ) { OnDuplicateRequested.ExecuteIfBound(ObjectsToDuplicate[0]); } else if ( ObjectsToDuplicate.Num() > 1 ) { TArray NewObjects; ObjectTools::DuplicateObjects(ObjectsToDuplicate, TEXT(""), TEXT(""), /*bOpenDialog=*/false, &NewObjects); TArray AssetsToSync; for ( auto ObjIt = NewObjects.CreateConstIterator(); ObjIt; ++ObjIt ) { new(AssetsToSync) FAssetData(*ObjIt); } // Sync to asset tree if ( NewObjects.Num() > 0 ) { OnFindInAssetTreeRequested.ExecuteIfBound(AssetsToSync); } } } void FAssetContextMenu::ExecuteRename() { TArray< FAssetData > AssetViewSelectedAssets = AssetView.Pin()->GetSelectedAssets(); TArray< FString > SelectedFolders = AssetView.Pin()->GetSelectedFolders(); if ( AssetViewSelectedAssets.Num() == 1 && SelectedFolders.Num() == 0 ) { // Don't operate on Redirectors if ( AssetViewSelectedAssets[0].AssetClass != UObjectRedirector::StaticClass()->GetFName() ) { OnRenameRequested.ExecuteIfBound(AssetViewSelectedAssets[0]); } } if ( AssetViewSelectedAssets.Num() == 0 && SelectedFolders.Num() == 1 ) { OnRenameFolderRequested.ExecuteIfBound(SelectedFolders[0]); } } void FAssetContextMenu::ExecuteDelete() { // Don't allow asset deletion during PIE if (GIsEditor) { UEditorEngine* Editor = GEditor; FWorldContext* PIEWorldContext = GEditor->GetPIEWorldContext(); if (PIEWorldContext) { FNotificationInfo Notification(LOCTEXT("CannotDeleteAssetInPIE", "Assets cannot be deleted while in PIE.")); Notification.ExpireDuration = 3.0f; FSlateNotificationManager::Get().AddNotification(Notification); return; } } TArray< FAssetData > AssetViewSelectedAssets = AssetView.Pin()->GetSelectedAssets(); if(AssetViewSelectedAssets.Num() > 0) { TArray AssetsToDelete; for( auto AssetIt = AssetViewSelectedAssets.CreateConstIterator(); AssetIt; ++AssetIt ) { const FAssetData& AssetData = *AssetIt; if( AssetData.AssetClass == UObjectRedirector::StaticClass()->GetFName() ) { // Don't operate on Redirectors continue; } AssetsToDelete.Add( AssetData ); } if ( AssetsToDelete.Num() > 0 ) { ObjectTools::DeleteAssets( AssetsToDelete ); } } TArray< FString > SelectedFolders = AssetView.Pin()->GetSelectedFolders(); if(SelectedFolders.Num() > 0) { FText Prompt; if ( SelectedFolders.Num() == 1 ) { Prompt = FText::Format(LOCTEXT("FolderDeleteConfirm_Single", "Delete folder '{0}'?"), FText::FromString(SelectedFolders[0])); } else { Prompt = FText::Format(LOCTEXT("FolderDeleteConfirm_Multiple", "Delete {0} folders?"), FText::AsNumber(SelectedFolders.Num())); } // Spawn a confirmation dialog since this is potentially a highly destructive operation ContentBrowserUtils::DisplayConfirmationPopup( Prompt, LOCTEXT("FolderDeleteConfirm_Yes", "Delete"), LOCTEXT("FolderDeleteConfirm_No", "Cancel"), AssetView.Pin().ToSharedRef(), FOnClicked::CreateSP( this, &FAssetContextMenu::ExecuteDeleteFolderConfirmed )); } } bool FAssetContextMenu::CanExecuteReload() const { TArray< FAssetData > AssetViewSelectedAssets = AssetView.Pin()->GetSelectedAssets(); TArray< FString > SelectedFolders = AssetView.Pin()->GetSelectedFolders(); int32 NumAssetItems, NumClassItems; ContentBrowserUtils::CountItemTypes(AssetViewSelectedAssets, NumAssetItems, NumClassItems); int32 NumAssetPaths, NumClassPaths; ContentBrowserUtils::CountPathTypes(SelectedFolders, NumAssetPaths, NumClassPaths); bool bHasSelectedCollections = false; for (const FString& SelectedFolder : SelectedFolders) { if (ContentBrowserUtils::IsCollectionPath(SelectedFolder)) { bHasSelectedCollections = true; break; } } // We can't reload classes, or folders containing classes, or any collection folders return ((NumAssetItems > 0 && NumClassItems == 0) || (NumAssetPaths > 0 && NumClassPaths == 0)) && !bHasSelectedCollections; } void FAssetContextMenu::ExecuteReload() { // Don't allow asset reload during PIE if (GIsEditor) { UEditorEngine* Editor = GEditor; FWorldContext* PIEWorldContext = GEditor->GetPIEWorldContext(); if (PIEWorldContext) { FNotificationInfo Notification(LOCTEXT("CannotReloadAssetInPIE", "Assets cannot be reloaded while in PIE.")); Notification.ExpireDuration = 3.0f; FSlateNotificationManager::Get().AddNotification(Notification); return; } } TArray AssetViewSelectedAssets = AssetView.Pin()->GetSelectedAssets(); if (AssetViewSelectedAssets.Num() > 0) { TArray PackagesToReload; for (auto AssetIt = AssetViewSelectedAssets.CreateConstIterator(); AssetIt; ++AssetIt) { const FAssetData& AssetData = *AssetIt; if (AssetData.AssetClass == UObjectRedirector::StaticClass()->GetFName()) { // Don't operate on Redirectors continue; } PackagesToReload.AddUnique(AssetData.GetPackage()); } if (PackagesToReload.Num() > 0) { UPackageTools::ReloadPackages(PackagesToReload); } } } FReply FAssetContextMenu::ExecuteDeleteFolderConfirmed() { TArray< FString > SelectedFolders = AssetView.Pin()->GetSelectedFolders(); if(SelectedFolders.Num() > 0) { ContentBrowserUtils::DeleteFolders(SelectedFolders); } return FReply::Handled(); } void FAssetContextMenu::ExecuteConsolidate() { TArray ObjectsToConsolidate; const bool SkipRedirectors = true; GetSelectedAssets(ObjectsToConsolidate, SkipRedirectors); if ( ObjectsToConsolidate.Num() > 0 ) { FConsolidateToolWindow::AddConsolidationObjects( ObjectsToConsolidate ); } } void FAssetContextMenu::ExecuteCaptureThumbnail() { FViewport* Viewport = GEditor->GetActiveViewport(); if ( ensure(GCurrentLevelEditingViewportClient) && ensure(Viewport) ) { //have to re-render the requested viewport FLevelEditorViewportClient* OldViewportClient = GCurrentLevelEditingViewportClient; //remove selection box around client during render GCurrentLevelEditingViewportClient = NULL; Viewport->Draw(); ContentBrowserUtils::CaptureThumbnailFromViewport(Viewport, SelectedAssets); //redraw viewport to have the yellow highlight again GCurrentLevelEditingViewportClient = OldViewportClient; Viewport->Draw(); } } void FAssetContextMenu::ExecuteClearThumbnail() { ContentBrowserUtils::ClearCustomThumbnails(SelectedAssets); } void FAssetContextMenu::ExecuteMigrateAsset() { // Get a list of package names for input into MigratePackages TArray PackageNames; PackageNames.Reserve(SelectedAssets.Num()); for (int32 AssetIdx = 0; AssetIdx < SelectedAssets.Num(); ++AssetIdx) { PackageNames.Add(SelectedAssets[AssetIdx].PackageName); } FAssetToolsModule& AssetToolsModule = FModuleManager::Get().LoadModuleChecked("AssetTools"); AssetToolsModule.Get().MigratePackages( PackageNames ); } void FAssetContextMenu::ExecuteGoToCodeForAsset(UClass* SelectedClass) { if (SelectedClass) { FString ClassHeaderPath; if( FSourceCodeNavigation::FindClassHeaderPath( SelectedClass, ClassHeaderPath ) && IFileManager::Get().FileSize( *ClassHeaderPath ) != INDEX_NONE ) { const FString AbsoluteHeaderPath = IFileManager::Get().ConvertToAbsolutePathForExternalAppForRead(*ClassHeaderPath); FSourceCodeNavigation::OpenSourceFile( AbsoluteHeaderPath ); } } } void FAssetContextMenu::ExecuteGoToDocsForAsset(UClass* SelectedClass) { ExecuteGoToDocsForAsset(SelectedClass, FString()); } void FAssetContextMenu::ExecuteGoToDocsForAsset(UClass* SelectedClass, const FString ExcerptSection) { if (SelectedClass) { FString DocumentationLink = FEditorClassUtils::GetDocumentationLink(SelectedClass, ExcerptSection); if (!DocumentationLink.IsEmpty()) { IDocumentation::Get()->Open(DocumentationLink, FDocumentationSourceInfo(TEXT("cb_docs"))); } } } void FAssetContextMenu::ExecuteCopyReference() { ContentBrowserUtils::CopyAssetReferencesToClipboard(SelectedAssets); } void FAssetContextMenu::ExecuteCopyTextToClipboard(FString InText) { FPlatformApplicationMisc::ClipboardCopy(*InText); } void FAssetContextMenu::ExecuteResetLocalizationId() { #if USE_STABLE_LOCALIZATION_KEYS const FText ResetLocalizationIdMsg = LOCTEXT("ResetLocalizationIdMsg", "This will reset the localization ID of the selected assets and cause all text within them to lose their existing translations.\n\nAre you sure you want to do this?"); if (FMessageDialog::Open(EAppMsgType::YesNo, ResetLocalizationIdMsg) != EAppReturnType::Yes) { return; } for (const FAssetData& AssetData : SelectedAssets) { UObject* Asset = AssetData.GetAsset(); if (Asset) { Asset->Modify(); TextNamespaceUtil::ClearPackageNamespace(Asset); TextNamespaceUtil::EnsurePackageNamespace(Asset); } } #endif // USE_STABLE_LOCALIZATION_KEYS } void FAssetContextMenu::ExecuteShowLocalizationCache(const FString InPackageFilename) { FString CachedLocalizationId; TArray GatherableTextDataArray; // Read the localization data from the cache in the package header { TUniquePtr FileReader(IFileManager::Get().CreateFileReader(*InPackageFilename)); if (FileReader) { // Read package file summary from the file FPackageFileSummary PackageFileSummary; *FileReader << PackageFileSummary; CachedLocalizationId = PackageFileSummary.LocalizationId; if (PackageFileSummary.GatherableTextDataOffset > 0) { FileReader->Seek(PackageFileSummary.GatherableTextDataOffset); GatherableTextDataArray.SetNum(PackageFileSummary.GatherableTextDataCount); for (int32 GatherableTextDataIndex = 0; GatherableTextDataIndex < PackageFileSummary.GatherableTextDataCount; ++GatherableTextDataIndex) { *FileReader << GatherableTextDataArray[GatherableTextDataIndex]; } } } } // Convert the gathered text array into a readable format FString LocalizationCacheStr = FString::Printf(TEXT("Package: %s"), *CachedLocalizationId); for (const FGatherableTextData& GatherableTextData : GatherableTextDataArray) { if (LocalizationCacheStr.Len() > 0) { LocalizationCacheStr += TEXT("\n\n"); } FString KeysStr; FString EditorOnlyKeysStr; for (const FTextSourceSiteContext& TextSourceSiteContext : GatherableTextData.SourceSiteContexts) { FString* KeysStrPtr = TextSourceSiteContext.IsEditorOnly ? &EditorOnlyKeysStr : &KeysStr; if (KeysStrPtr->Len() > 0) { *KeysStrPtr += TEXT(", "); } *KeysStrPtr += TextSourceSiteContext.KeyName; } LocalizationCacheStr += FString::Printf(TEXT("Namespace: %s\n"), *GatherableTextData.NamespaceName); if (KeysStr.Len() > 0) { LocalizationCacheStr += FString::Printf(TEXT("Keys: %s\n"), *KeysStr); } if (EditorOnlyKeysStr.Len() > 0) { LocalizationCacheStr += FString::Printf(TEXT("Keys (Editor-Only): %s\n"), *EditorOnlyKeysStr); } LocalizationCacheStr += FString::Printf(TEXT("Source: %s"), *GatherableTextData.SourceData.SourceString); } // Generate a message box for the result SGenericDialogWidget::OpenDialog(LOCTEXT("LocalizationCache", "Localization Cache"), SNew(SBox) .MaxDesiredWidth(800.0f) .MaxDesiredHeight(400.0f) [ SNew(SMultiLineEditableTextBox) .IsReadOnly(true) .AutoWrapText(true) .Text(FText::AsCultureInvariant(LocalizationCacheStr)) ], SGenericDialogWidget::FArguments() .UseScrollBox(false) ); } void FAssetContextMenu::ExecuteExport() { TArray ObjectsToExport; const bool SkipRedirectors = false; GetSelectedAssets(ObjectsToExport, SkipRedirectors); if ( ObjectsToExport.Num() > 0 ) { FAssetToolsModule& AssetToolsModule = FModuleManager::GetModuleChecked("AssetTools"); AssetToolsModule.Get().ExportAssetsWithDialog(ObjectsToExport, true); } } void FAssetContextMenu::ExecuteBulkExport() { TArray ObjectsToExport; const bool SkipRedirectors = false; GetSelectedAssets(ObjectsToExport, SkipRedirectors); if ( ObjectsToExport.Num() > 0 ) { FAssetToolsModule& AssetToolsModule = FModuleManager::GetModuleChecked("AssetTools"); AssetToolsModule.Get().ExportAssetsWithDialog(ObjectsToExport, false); } } void FAssetContextMenu::ExecuteRemoveFromCollection() { if ( ensure(SourcesData.Collections.Num() == 1) ) { TArray AssetsToRemove; for (auto AssetIt = SelectedAssets.CreateConstIterator(); AssetIt; ++AssetIt) { AssetsToRemove.Add((*AssetIt).ObjectPath); } if ( AssetsToRemove.Num() > 0 ) { FCollectionManagerModule& CollectionManagerModule = FCollectionManagerModule::GetModule(); const FCollectionNameType& Collection = SourcesData.Collections[0]; CollectionManagerModule.Get().RemoveFromCollection(Collection.Name, Collection.Type, AssetsToRemove); OnAssetViewRefreshRequested.ExecuteIfBound(); } } } void FAssetContextMenu::ExecuteSCCRefresh() { TArray PackageNames; GetSelectedPackageNames(PackageNames); ISourceControlModule::Get().GetProvider().Execute(ISourceControlOperation::Create(), SourceControlHelpers::PackageFilenames(PackageNames), EConcurrency::Asynchronous); } void FAssetContextMenu::ExecuteSCCMerge() { FAssetToolsModule& AssetToolsModule = FModuleManager::GetModuleChecked("AssetTools"); for (int32 AssetIdx = 0; AssetIdx < SelectedAssets.Num(); AssetIdx++) { // Get the actual asset (will load it) const FAssetData& AssetData = SelectedAssets[AssetIdx]; UObject* CurrentObject = AssetData.GetAsset(); if (CurrentObject) { const FString PackagePath = AssetData.PackageName.ToString(); const FString PackageName = AssetData.AssetName.ToString(); auto AssetTypeActions = AssetToolsModule.Get().GetAssetTypeActionsForClass( CurrentObject->GetClass() ).Pin(); if( AssetTypeActions.IsValid() ) { AssetTypeActions->Merge(CurrentObject); } } } } void FAssetContextMenu::ExecuteSCCCheckOut() { TArray PackagesToCheckOut; GetSelectedPackages(PackagesToCheckOut); if ( PackagesToCheckOut.Num() > 0 ) { // Update the source control status of all potentially relevant packages if (ISourceControlModule::Get().GetProvider().Execute(ISourceControlOperation::Create(), PackagesToCheckOut) == ECommandResult::Succeeded) { // Now check them out FEditorFileUtils::CheckoutPackages(PackagesToCheckOut); } } } void FAssetContextMenu::ExecuteSCCOpenForAdd() { TArray PackageNames; GetSelectedPackageNames(PackageNames); ISourceControlProvider& SourceControlProvider = ISourceControlModule::Get().GetProvider(); 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()); } } } SourceControlProvider.Execute(ISourceControlOperation::Create(), SourceControlHelpers::PackageFilenames(PackagesToAdd)); } } void FAssetContextMenu::ExecuteSCCCheckIn() { TArray Packages; GetSelectedPackages(Packages); // 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( Packages, 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 PackageNames; GetSelectedPackageNames(PackageNames); const bool bUseSourceControlStateCache = true; const bool bCheckinGood = FSourceControlWindows::PromptForCheckin(bUseSourceControlStateCache, PackageNames); if (!bCheckinGood) { FMessageDialog::Open(EAppMsgType::Ok, NSLOCTEXT("UnrealEd", "SCC_Checkin_Failed", "Check-in failed as a result of save failure.")); } } 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 FAssetContextMenu::ExecuteSCCHistory() { TArray PackageNames; GetSelectedPackageNames(PackageNames); FSourceControlWindows::DisplayRevisionHistory(SourceControlHelpers::PackageFilenames(PackageNames)); } void FAssetContextMenu::ExecuteSCCDiffAgainstDepot() const { // Load the asset registry module FAssetToolsModule& AssetToolsModule = FModuleManager::GetModuleChecked("AssetTools"); // Iterate over each selected asset for(int32 AssetIdx=0; AssetIdx PackageNames; GetSelectedPackageNames(PackageNames); FSourceControlWindows::PromptForRevert(PackageNames); } void FAssetContextMenu::ExecuteSCCSync() { TArray PackageNames; GetSelectedPackageNames(PackageNames); ContentBrowserUtils::SyncPackagesFromSourceControl(PackageNames); } void FAssetContextMenu::ExecuteEnableSourceControl() { ISourceControlModule::Get().ShowLoginDialog(FSourceControlLoginClosed(), ELoginWindowMode::Modeless); } bool FAssetContextMenu::CanExecuteSyncToAssetTree() const { return SelectedAssets.Num() > 0; } bool FAssetContextMenu::CanExecuteFindInExplorer() const { // selection must contain at least one asset that has already been saved to disk for (const FAssetData& Asset : SelectedAssets) { if ((Asset.PackageFlags & PKG_NewlyCreated) == 0) { return true; } } return false; } bool FAssetContextMenu::CanExecuteCreateBlueprintUsing() const { // Only work if you have a single asset selected if(SelectedAssets.Num() == 1) { UObject* Asset = SelectedAssets[0].GetAsset(); // See if we know how to make a component from this asset TArray< TSubclassOf > ComponentClassList = FComponentAssetBrokerage::GetComponentsForAsset(Asset); return (ComponentClassList.Num() > 0); } return false; } bool FAssetContextMenu::CanExecuteFindAssetInWorld() const { return bAtLeastOneNonRedirectorSelected; } bool FAssetContextMenu::CanExecuteProperties() const { return bAtLeastOneNonRedirectorSelected; } bool FAssetContextMenu::CanExecutePropertyMatrix(FText& OutErrorMessage) const { bool bResult = bAtLeastOneNonRedirectorSelected; if (bAtLeastOneNonRedirectorSelected) { TArray ObjectsForPropertiesMenu; const bool SkipRedirectors = true; GetSelectedAssets(ObjectsForPropertiesMenu, SkipRedirectors); // Ensure all Blueprints are valid. for (UObject* Object : ObjectsForPropertiesMenu) { if (UBlueprint* BlueprintObj = Cast(Object)) { if (BlueprintObj->GeneratedClass == nullptr) { OutErrorMessage = LOCTEXT("InvalidBlueprint", "A selected Blueprint is invalid."); bResult = false; break; } } } } return bResult; } bool FAssetContextMenu::CanExecutePropertyMatrix() const { FText ErrorMessageDummy; return CanExecutePropertyMatrix(ErrorMessageDummy); } FText FAssetContextMenu::GetExecutePropertyMatrixTooltip() const { FText ResultTooltip; if (CanExecutePropertyMatrix(ResultTooltip)) { ResultTooltip = LOCTEXT("PropertyMatrixTooltip", "Opens the property matrix editor for the selected assets."); } return ResultTooltip; } bool FAssetContextMenu::CanExecuteShowAssetMetaData() const { TArray ObjectsForPropertiesMenu; const bool SkipRedirectors = true; GetSelectedAssets(ObjectsForPropertiesMenu, SkipRedirectors); bool bResult = false; for (const UObject* Asset : ObjectsForPropertiesMenu) { if (Asset && UMetaData::GetMapForObject(Asset)) { bResult = true; break; } } return bResult; } bool FAssetContextMenu::CanExecuteDuplicate() const { const TArray< FAssetData > AssetViewSelectedAssets = AssetView.Pin()->GetSelectedAssets(); uint32 NumNonRedirectors = 0; for (const FAssetData& AssetData : AssetViewSelectedAssets) { if (!AssetData.IsValid()) { continue; } if (AssetData.AssetClass == NAME_Class) { return false; } if (AssetData.AssetClass != UObjectRedirector::StaticClass()->GetFName()) { ++NumNonRedirectors; } } return (NumNonRedirectors > 0); } bool FAssetContextMenu::CanExecuteRename() const { return ContentBrowserUtils::CanRenameFromAssetView(AssetView); } bool FAssetContextMenu::CanExecuteDelete() const { return ContentBrowserUtils::CanDeleteFromAssetView(AssetView); } bool FAssetContextMenu::CanExecuteRemoveFromCollection() const { return SourcesData.Collections.Num() == 1 && !SourcesData.IsDynamicCollection(); } bool FAssetContextMenu::CanExecuteSCCRefresh() const { return ISourceControlModule::Get().IsEnabled(); } bool FAssetContextMenu::CanExecuteSCCMerge() const { FAssetToolsModule& AssetToolsModule = FModuleManager::GetModuleChecked("AssetTools"); bool bCanExecuteMerge = bCanExecuteSCCMerge; for (int32 AssetIdx = 0; AssetIdx < SelectedAssets.Num() && bCanExecuteMerge; AssetIdx++) { // Get the actual asset (will load it) const FAssetData& AssetData = SelectedAssets[AssetIdx]; UObject* CurrentObject = AssetData.GetAsset(); if (CurrentObject) { auto AssetTypeActions = AssetToolsModule.Get().GetAssetTypeActionsForClass(CurrentObject->GetClass()).Pin(); if (AssetTypeActions.IsValid()) { bCanExecuteMerge = AssetTypeActions->CanMerge(); } } else { bCanExecuteMerge = false; } } return bCanExecuteMerge; } bool FAssetContextMenu::CanExecuteSCCCheckOut() const { return bCanExecuteSCCCheckOut; } bool FAssetContextMenu::CanExecuteSCCOpenForAdd() const { return bCanExecuteSCCOpenForAdd; } bool FAssetContextMenu::CanExecuteSCCCheckIn() const { return bCanExecuteSCCCheckIn; } bool FAssetContextMenu::CanExecuteSCCHistory() const { return bCanExecuteSCCHistory; } bool FAssetContextMenu::CanExecuteSCCDiffAgainstDepot() const { return bCanExecuteSCCHistory; } bool FAssetContextMenu::CanExecuteSCCRevert() const { return bCanExecuteSCCRevert; } bool FAssetContextMenu::CanExecuteSCCSync() const { return bCanExecuteSCCSync; } bool FAssetContextMenu::CanExecuteConsolidate() const { TArray ProposedObjects; for (int32 AssetIdx = 0; AssetIdx < SelectedAssets.Num(); ++AssetIdx) { // Don't load assets here. Only operate on already loaded assets. if ( SelectedAssets[AssetIdx].IsAssetLoaded() ) { UObject* Object = SelectedAssets[AssetIdx].GetAsset(); if ( Object ) { ProposedObjects.Add(Object); } } } if ( ProposedObjects.Num() > 0 ) { TArray CompatibleObjects; return FConsolidateToolWindow::DetermineAssetCompatibility(ProposedObjects, CompatibleObjects); } return false; } bool FAssetContextMenu::CanExecuteSaveAsset() const { if ( bAtLeastOneClassSelected ) { return false; } TArray Packages; GetSelectedPackages(Packages); // only enabled if at least one selected package is loaded at all for (int32 PackageIdx = 0; PackageIdx < Packages.Num(); ++PackageIdx) { if ( Packages[PackageIdx] != NULL ) { return true; } } return false; } bool FAssetContextMenu::CanExecuteDiffSelected() const { bool bCanDiffSelected = false; if (SelectedAssets.Num() == 2 && !bAtLeastOneClassSelected) { FAssetData const& FirstSelection = SelectedAssets[0]; FAssetData const& SecondSelection = SelectedAssets[1]; bCanDiffSelected = FirstSelection.AssetClass == SecondSelection.AssetClass; } return bCanDiffSelected; } bool FAssetContextMenu::CanExecuteCaptureThumbnail() const { return GCurrentLevelEditingViewportClient != NULL; } bool FAssetContextMenu::CanClearCustomThumbnails() const { for ( auto AssetIt = SelectedAssets.CreateConstIterator(); AssetIt; ++AssetIt ) { if ( ContentBrowserUtils::AssetHasCustomThumbnail(*AssetIt) ) { return true; } } return false; } void FAssetContextMenu::CacheCanExecuteVars() { bAtLeastOneNonRedirectorSelected = false; bAtLeastOneClassSelected = false; bCanExecuteSCCMerge = false; bCanExecuteSCCCheckOut = false; bCanExecuteSCCOpenForAdd = false; bCanExecuteSCCCheckIn = false; bCanExecuteSCCHistory = false; bCanExecuteSCCRevert = false; bCanExecuteSCCSync = false; for (auto AssetIt = SelectedAssets.CreateConstIterator(); AssetIt; ++AssetIt) { const FAssetData& AssetData = *AssetIt; if ( !AssetData.IsValid() ) { continue; } if ( !bAtLeastOneNonRedirectorSelected && AssetData.AssetClass != UObjectRedirector::StaticClass()->GetFName() ) { bAtLeastOneNonRedirectorSelected = true; } bAtLeastOneClassSelected |= AssetData.AssetClass == NAME_Class; ISourceControlProvider& SourceControlProvider = ISourceControlModule::Get().GetProvider(); if ( ISourceControlModule::Get().IsEnabled() ) { // Check the SCC state for each package in the selected paths FSourceControlStatePtr SourceControlState = SourceControlProvider.GetState(SourceControlHelpers::PackageFilename(AssetData.PackageName.ToString()), EStateCacheUsage::Use); if(SourceControlState.IsValid()) { if (SourceControlState->IsConflicted() ) { bCanExecuteSCCMerge = true; } if ( SourceControlState->CanCheckout() ) { bCanExecuteSCCCheckOut = true; } if ( !SourceControlState->IsSourceControlled() && SourceControlState->CanAdd() ) { bCanExecuteSCCOpenForAdd = true; } else if( SourceControlState->IsSourceControlled() && !SourceControlState->IsAdded() ) { bCanExecuteSCCHistory = true; } if(!SourceControlState->IsCurrent()) { bCanExecuteSCCSync = true; } if ( SourceControlState->CanCheckIn() ) { bCanExecuteSCCCheckIn = true; } if (SourceControlState->CanRevert()) { bCanExecuteSCCRevert = true; } } } if ( bAtLeastOneNonRedirectorSelected && bAtLeastOneClassSelected && bCanExecuteSCCMerge && bCanExecuteSCCCheckOut && bCanExecuteSCCOpenForAdd && bCanExecuteSCCCheckIn && bCanExecuteSCCHistory && bCanExecuteSCCRevert && bCanExecuteSCCSync ) { // All options are available, no need to keep iterating break; } } } void FAssetContextMenu::GetSelectedPackageNames(TArray& OutPackageNames) const { for (int32 AssetIdx = 0; AssetIdx < SelectedAssets.Num(); ++AssetIdx) { OutPackageNames.Add(SelectedAssets[AssetIdx].PackageName.ToString()); } } void FAssetContextMenu::GetSelectedPackages(TArray& OutPackages) const { for (int32 AssetIdx = 0; AssetIdx < SelectedAssets.Num(); ++AssetIdx) { UPackage* Package = FindPackage(NULL, *SelectedAssets[AssetIdx].PackageName.ToString()); if ( Package ) { OutPackages.Add(Package); } } } void FAssetContextMenu::MakeChunkIDListMenu(FMenuBuilder& MenuBuilder) { TArray FoundChunks; TArray< FAssetData > AssetViewSelectedAssets = AssetView.Pin()->GetSelectedAssets(); for (const auto& SelectedAsset : AssetViewSelectedAssets) { UPackage* Package = FindPackage(NULL, *SelectedAsset.PackageName.ToString()); if (Package) { for (auto ChunkID : Package->GetChunkIDs()) { FoundChunks.AddUnique(ChunkID); } } } for (auto ChunkID : FoundChunks) { MenuBuilder.AddMenuEntry( FText::Format(LOCTEXT("PackageChunk", "Chunk {0}"), FText::AsNumber(ChunkID)), FText(), FSlateIcon(), FUIAction( FExecuteAction::CreateSP(this, &FAssetContextMenu::ExecuteRemoveChunkID, ChunkID) ) ); } } void FAssetContextMenu::ExecuteAssignChunkID() { TArray< FAssetData > AssetViewSelectedAssets = AssetView.Pin()->GetSelectedAssets(); auto AssetViewPtr = AssetView.Pin(); if (AssetViewSelectedAssets.Num() > 0 && AssetViewPtr.IsValid()) { // Determine the position of the window so that it will spawn near the mouse, but not go off the screen. const FVector2D CursorPos = FSlateApplication::Get().GetCursorPos(); FSlateRect Anchor(CursorPos.X, CursorPos.Y, CursorPos.X, CursorPos.Y); FVector2D AdjustedSummonLocation = FSlateApplication::Get().CalculatePopupWindowPosition(Anchor, SColorPicker::DEFAULT_WINDOW_SIZE, true, FVector2D::ZeroVector, Orient_Horizontal); TSharedPtr Window = SNew(SWindow) .AutoCenter(EAutoCenter::None) .ScreenPosition(AdjustedSummonLocation) .SupportsMaximize(false) .SupportsMinimize(false) .SizingRule(ESizingRule::Autosized) .Title(LOCTEXT("WindowHeader", "Enter Chunk ID")); Window->SetContent( SNew(SVerticalBox) + SVerticalBox::Slot() .FillHeight(1.0f) .HAlign(HAlign_Fill) .VAlign(VAlign_Top) [ SNew(SHorizontalBox) + SHorizontalBox::Slot() .FillWidth(1.0f) .HAlign(HAlign_Left) .VAlign(VAlign_Center) [ SNew(STextBlock) .Text(LOCTEXT("MeshPaint_LabelStrength", "Chunk ID")) ] + SHorizontalBox::Slot() .FillWidth(2.0f) .HAlign(HAlign_Fill) .VAlign(VAlign_Center) [ SNew(SNumericEntryBox) .AllowSpin(true) .MinSliderValue(0) .MaxSliderValue(300) .MinValue(0) .MaxValue(300) .Value(this, &FAssetContextMenu::GetChunkIDSelection) .OnValueChanged(this, &FAssetContextMenu::OnChunkIDAssignChanged) ] ] + SVerticalBox::Slot() .FillHeight(1.0f) .HAlign(HAlign_Fill) .VAlign(VAlign_Bottom) [ SNew(SHorizontalBox) + SHorizontalBox::Slot() .FillWidth(1.0f) .HAlign(HAlign_Right) .VAlign(VAlign_Center) [ SNew(SButton) .Text(LOCTEXT("ChunkIDAssign_Yes", "OK")) .OnClicked(this, &FAssetContextMenu::OnChunkIDAssignCommit, Window) ] + SHorizontalBox::Slot() .FillWidth(1.0f) .HAlign(HAlign_Left) .VAlign(VAlign_Center) [ SNew(SButton) .Text(LOCTEXT("ChunkIDAssign_No", "Cancel")) .OnClicked(this, &FAssetContextMenu::OnChunkIDAssignCancel, Window) ] ] ); ChunkIDSelected = 0; FSlateApplication::Get().AddModalWindow(Window.ToSharedRef(), AssetViewPtr); } } void FAssetContextMenu::ExecuteRemoveAllChunkID() { TArray EmptyChunks; TArray< FAssetData > AssetViewSelectedAssets = AssetView.Pin()->GetSelectedAssets(); for (const auto& SelectedAsset : AssetViewSelectedAssets) { UPackage* Package = FindPackage(NULL, *SelectedAsset.PackageName.ToString()); if (Package) { Package->SetChunkIDs(EmptyChunks); Package->SetDirtyFlag(true); } } } TOptional FAssetContextMenu::GetChunkIDSelection() const { return ChunkIDSelected; } void FAssetContextMenu::OnChunkIDAssignChanged(int32 NewChunkID) { ChunkIDSelected = NewChunkID; } FReply FAssetContextMenu::OnChunkIDAssignCommit(TSharedPtr Window) { TArray< FAssetData > AssetViewSelectedAssets = AssetView.Pin()->GetSelectedAssets(); for (const auto& SelectedAsset : AssetViewSelectedAssets) { UPackage* Package = FindPackage(NULL, *SelectedAsset.PackageName.ToString()); if (Package) { TArray CurrentChunks = Package->GetChunkIDs(); CurrentChunks.AddUnique(ChunkIDSelected); Package->SetChunkIDs(CurrentChunks); Package->SetDirtyFlag(true); } } Window->RequestDestroyWindow(); return FReply::Handled(); } FReply FAssetContextMenu::OnChunkIDAssignCancel(TSharedPtr Window) { Window->RequestDestroyWindow(); return FReply::Handled(); } void FAssetContextMenu::ExecuteRemoveChunkID(int32 ChunkID) { TArray< FAssetData > AssetViewSelectedAssets = AssetView.Pin()->GetSelectedAssets(); for (const auto& SelectedAsset : AssetViewSelectedAssets) { UPackage* Package = FindPackage(NULL, *SelectedAsset.PackageName.ToString()); if (Package) { int32 FoundIndex; TArray CurrentChunks = Package->GetChunkIDs(); CurrentChunks.Find(ChunkID, FoundIndex); if (FoundIndex != INDEX_NONE) { CurrentChunks.RemoveAt(FoundIndex); Package->SetChunkIDs(CurrentChunks); Package->SetDirtyFlag(true); } } } } #undef LOCTEXT_NAMESPACE