// Copyright Epic Games, Inc. All Rights Reserved. #include "AssetContextMenu.h" #include "AssetRegistry/AssetData.h" #include "ToolMenus.h" #include "ContentBrowserMenuContexts.h" #include "ContentBrowserDataSource.h" #include "IAssetTools.h" #include "AssetToolsModule.h" #include "ContentBrowserUtils.h" #include "SAssetView.h" #include "ContentBrowserModule.h" #include "ContentBrowserSingleton.h" #include "Styling/AppStyle.h" #include "HAL/FileManager.h" #include "ICollectionManager.h" #include "CollectionManagerModule.h" #include "CollectionAssetManagement.h" #include "Toolkits/GlobalEditorCommonCommands.h" #include "Framework/Commands/GenericCommands.h" #include "ContentBrowserCommands.h" #include "Misc/PathViews.h" namespace ContentBrowserConsoleVariables { static bool ContentBrowser_EnablePublicAssetFeature = false; static FAutoConsoleVariableRef ContentBrowser_EnablePublicAssetFeatureCVar(TEXT("ContentBrowser.PublicAsset.EnablePublicAssetFeature"), ContentBrowser_EnablePublicAssetFeature, TEXT("Enables the Experimental Public Asset Feature (False: disabled, True:enabled")); } #define LOCTEXT_NAMESPACE "ContentBrowser" FAssetContextMenu::FAssetContextMenu(const TWeakPtr& InAssetView) : AssetView(InAssetView) { } void FAssetContextMenu::BindCommands(TSharedPtr< FUICommandList >& Commands) { Commands->MapAction(FGenericCommands::Get().Duplicate, FUIAction( FExecuteAction::CreateSP(this, &FAssetContextMenu::ExecuteDuplicate), FCanExecuteAction::CreateSP(this, &FAssetContextMenu::CanExecuteDuplicate) )); Commands->MapAction(FGlobalEditorCommonCommands::Get().FindInContentBrowser, FUIAction( FExecuteAction::CreateSP(this, &FAssetContextMenu::ExecuteSyncToAssetTree), FCanExecuteAction::CreateSP(this, &FAssetContextMenu::CanExecuteSyncToAssetTree) )); } TSharedRef FAssetContextMenu::MakeContextMenu(TArrayView InSelectedItems, const FSourcesData& InSourcesData, TSharedPtr< FUICommandList > InCommandList) { SetSelectedItems(InSelectedItems); 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(); TSharedPtr MenuExtender; { TArray SelectedAssets; for (const FContentBrowserItem& SelectedFile : SelectedFiles) { FAssetData ItemAssetData; if (SelectedFile.Legacy_TryGetAssetData(ItemAssetData)) { SelectedAssets.Add(ItemAssetData); } } if (SelectedAssets.Num() > 0) { TArray> Extenders; for (int32 i = 0; i < MenuExtenderDelegates.Num(); ++i) { if (MenuExtenderDelegates[i].IsBound()) { Extenders.Add(MenuExtenderDelegates[i].Execute(SelectedAssets)); } } MenuExtender = FExtender::Combine(Extenders); } } UContentBrowserAssetContextMenuContext* ContextObject = NewObject(); ContextObject->AssetContextMenu = SharedThis(this); UToolMenus* ToolMenus = UToolMenus::Get(); static const FName BaseMenuName("ContentBrowser.AssetContextMenu"); static const FName ItemContextMenuName("ContentBrowser.ItemContextMenu"); RegisterContextMenu(BaseMenuName); TArray SelectedObjects; // Create menu hierarchy based on class hierarchy FName MenuName = BaseMenuName; { // TODO: Ideally all of this asset specific stuff would happen in the asset data source, however we // need to keep it here for now to build the correct menu name and register the correct extenders // Objects must be loaded for this operation... for now TArray ObjectPaths; UContentBrowserDataSource* CommonDataSource = nullptr; bool bKeepCheckingCommonDataSource = true; for (const FContentBrowserItem& SelectedItem : SelectedItems) { if (bKeepCheckingCommonDataSource) { if (const FContentBrowserItemData* PrimaryInternalItem = SelectedItem.GetPrimaryInternalItem()) { if (UContentBrowserDataSource* OwnerDataSource = PrimaryInternalItem->GetOwnerDataSource()) { if (CommonDataSource == nullptr) { CommonDataSource = OwnerDataSource; } else if (CommonDataSource != OwnerDataSource) { CommonDataSource = nullptr; bKeepCheckingCommonDataSource = false; } } } } FAssetData ItemAssetData; if (SelectedItem.Legacy_TryGetAssetData(ItemAssetData)) { ObjectPaths.Add(ItemAssetData.ObjectPath.ToString()); } } FAssetToolsModule& AssetToolsModule = FModuleManager::LoadModuleChecked("AssetTools"); const TSharedRef& WritableFolderFilter = AssetToolsModule.Get().GetWritableFolderPermissionList(); ContextObject->bCanBeModified = ObjectPaths.Num() == 0; ContextObject->SelectedObjects.Reset(); if (ContentBrowserUtils::LoadAssetsIfNeeded(ObjectPaths, SelectedObjects) && SelectedObjects.Num() > 0) { ContextObject->SelectedObjects.Append(SelectedObjects); // Find common class for selected objects UClass* CommonClass = SelectedObjects[0]->GetClass(); for (int32 ObjIdx = 1; ObjIdx < SelectedObjects.Num(); ++ObjIdx) { while (!SelectedObjects[ObjIdx]->IsA(CommonClass)) { CommonClass = CommonClass->GetSuperClass(); } } ContextObject->CommonClass = CommonClass; ContextObject->bCanBeModified = true; for (const UObject* SelectedObject : SelectedObjects) { if (SelectedObject) { if (const UPackage* SelectedObjectPackage = SelectedObject->GetOutermost()) { if (WritableFolderFilter->HasFiltering() && !WritableFolderFilter->PassesStartsWithFilter(SelectedObjectPackage->GetFName())) { ContextObject->bCanBeModified = false; break; } if (SelectedObjectPackage->HasAnyPackageFlags(PKG_Cooked | PKG_FilterEditorOnly)) { ContextObject->bCanBeModified = false; break; } } if (const UClass* AssetClass = SelectedObject->GetClass()) { if (AssetClass->IsChildOf()) { ContextObject->bCanBeModified = false; break; } } } } MenuName = UToolMenus::JoinMenuPaths(BaseMenuName, CommonClass->GetFName()); RegisterMenuHierarchy(CommonClass); // Find asset actions for common class TSharedPtr CommonAssetTypeActions = AssetToolsModule.Get().GetAssetTypeActionsForClass(ContextObject->CommonClass).Pin(); if (CommonAssetTypeActions.IsValid() && CommonAssetTypeActions->HasActions(SelectedObjects)) { ContextObject->CommonAssetTypeActions = CommonAssetTypeActions; } } else if (SelectedObjects.Num() == 0) { if (CommonDataSource) { ContextObject->bCanBeModified = true; if (WritableFolderFilter->HasFiltering()) { for (const FContentBrowserItem& SelectedItem : SelectedItems) { if (!WritableFolderFilter->PassesStartsWithFilter(SelectedItem.GetVirtualPath())) { ContextObject->bCanBeModified = false; break; } } } MenuName = UToolMenus::JoinMenuPaths(ItemContextMenuName, CommonDataSource->GetFName()); if (!ToolMenus->IsMenuRegistered(MenuName)) { ToolMenus->RegisterMenu(MenuName, BaseMenuName); } } } } FToolMenuContext MenuContext(InCommandList, MenuExtender, ContextObject); { UContentBrowserDataMenuContext_FileMenu* DataContextObject = NewObject(); DataContextObject->SelectedItems = SelectedItems; DataContextObject->SelectedCollections = SourcesData.Collections; DataContextObject->bCanBeModified = ContextObject->bCanBeModified; DataContextObject->ParentWidget = AssetView; DataContextObject->OnShowInPathsView = OnShowInPathsViewRequested; DataContextObject->OnRefreshView = OnAssetViewRefreshRequested; MenuContext.AddObject(DataContextObject); } return ToolMenus->GenerateWidget(MenuName, MenuContext); } void FAssetContextMenu::RegisterMenuHierarchy(UClass* InClass) { static const FName BaseMenuName("ContentBrowser.AssetContextMenu"); UToolMenus* ToolMenus = UToolMenus::Get(); for (UClass* CurrentClass = InClass; CurrentClass; CurrentClass = CurrentClass->GetSuperClass()) { FName CurrentMenuName = UToolMenus::JoinMenuPaths(BaseMenuName, CurrentClass->GetFName()); if (!ToolMenus->IsMenuRegistered(CurrentMenuName)) { FName ParentMenuName; UClass* ParentClass = CurrentClass->GetSuperClass(); if (ParentClass == UObject::StaticClass() || ParentClass == nullptr) { ParentMenuName = BaseMenuName; } else { ParentMenuName = UToolMenus::JoinMenuPaths(BaseMenuName, ParentClass->GetFName()); } ToolMenus->RegisterMenu(CurrentMenuName, ParentMenuName); if (ParentMenuName == BaseMenuName) { break; } } } } void FAssetContextMenu::RegisterContextMenu(const FName MenuName) { UToolMenus* ToolMenus = UToolMenus::Get(); if (!ToolMenus->IsMenuRegistered(MenuName)) { UToolMenu* Menu = ToolMenus->RegisterMenu(MenuName); FToolMenuSection& Section = Menu->FindOrAddSection("GetAssetActions"); // Note: Do not use "GetActions" again when copying this code, otherwise "GetActions" menu entry will be overwritten Section.AddDynamicEntry("GetActions", FNewToolMenuSectionDelegate::CreateLambda([](FToolMenuSection& InSection) { UContentBrowserAssetContextMenuContext* Context = InSection.FindContext(); if (Context && Context->CommonAssetTypeActions.IsValid()) { Context->CommonAssetTypeActions.Pin()->GetActions(Context->GetSelectedObjects(), InSection); } })); Section.AddDynamicEntry("GetActionsLegacy", FNewToolMenuDelegateLegacy::CreateLambda([](FMenuBuilder& MenuBuilder, UToolMenu* InMenu) { UContentBrowserAssetContextMenuContext* Context = InMenu->FindContext(); if (Context && Context->CommonAssetTypeActions.IsValid()) { Context->CommonAssetTypeActions.Pin()->GetActions(Context->GetSelectedObjects(), MenuBuilder); } })); Menu->AddDynamicSection("AddMenuOptions", FNewToolMenuDelegate::CreateLambda([](UToolMenu* InMenu) { UContentBrowserAssetContextMenuContext* Context = InMenu->FindContext(); if (Context && Context->AssetContextMenu.IsValid()) { Context->AssetContextMenu.Pin()->AddMenuOptions(InMenu); } })); } } void FAssetContextMenu::AddMenuOptions(UToolMenu* InMenu) { UContentBrowserDataMenuContext_FileMenu* Context = InMenu->FindContext(); const bool bCanBeModified = !Context || Context->bCanBeModified; // Add any type-specific context menu options AddAssetTypeMenuOptions(InMenu); // Add quick access to common commands. AddCommonMenuOptions(InMenu); // Add quick access to view commands AddExploreMenuOptions(InMenu); if (ContentBrowserConsoleVariables::ContentBrowser_EnablePublicAssetFeature) { AddPublicStateMenuOptions(InMenu); } // Add reference options AddReferenceMenuOptions(InMenu); // Add collection options if (bCanBeModified) { AddCollectionMenuOptions(InMenu); } } void FAssetContextMenu::SetSelectedItems(TArrayView InSelectedItems) { SelectedItems.Reset(); SelectedItems.Append(InSelectedItems.GetData(), InSelectedItems.Num()); SelectedFiles.Reset(); SelectedFolders.Reset(); for (const FContentBrowserItem& SelectedItem : SelectedItems) { if (SelectedItem.IsFile()) { SelectedFiles.Add(SelectedItem); } if (SelectedItem.IsFolder()) { SelectedFolders.Add(SelectedItem); } } } void FAssetContextMenu::SetOnShowInPathsViewRequested(const FOnShowInPathsViewRequested& InOnShowInPathsViewRequested) { OnShowInPathsViewRequested = InOnShowInPathsViewRequested; } void FAssetContextMenu::SetOnRenameRequested(const FOnRenameRequested& InOnRenameRequested) { OnRenameRequested = InOnRenameRequested; } void FAssetContextMenu::SetOnDuplicateRequested(const FOnDuplicateRequested& InOnDuplicateRequested) { OnDuplicateRequested = InOnDuplicateRequested; } void FAssetContextMenu::SetOnEditRequested(const FOnEditRequested& InOnEditRequested) { OnEditRequested = InOnEditRequested; } void FAssetContextMenu::SetOnAssetViewRefreshRequested(const FOnAssetViewRefreshRequested& InOnAssetViewRefreshRequested) { OnAssetViewRefreshRequested = InOnAssetViewRefreshRequested; } bool FAssetContextMenu::AddCommonMenuOptions(UToolMenu* Menu) { UContentBrowserDataMenuContext_FileMenu* Context = Menu->FindContext(); const bool bCanBeModified = !Context || Context->bCanBeModified; { FToolMenuSection& Section = Menu->AddSection("CommonAssetActions", LOCTEXT("CommonAssetActionsMenuHeading", "Common")); if (bCanBeModified) { // Edit Section.AddMenuEntry( "EditAsset", LOCTEXT("EditAsset", "Edit..."), LOCTEXT("EditAssetTooltip", "Opens the selected item(s) for edit."), FSlateIcon(FAppStyle::GetAppStyleSetName(), "Icons.Edit"), FUIAction( FExecuteAction::CreateSP(this, &FAssetContextMenu::ExecuteEditItems), FCanExecuteAction::CreateSP(this, &FAssetContextMenu::CanExecuteEditItems) ) ); // Rename Section.AddMenuEntry(FGenericCommands::Get().Rename, LOCTEXT("Rename", "Rename"), LOCTEXT("RenameTooltip", "Rename the selected item.") ); // Duplicate Section.AddMenuEntry(FGenericCommands::Get().Duplicate, LOCTEXT("Duplicate", "Duplicate"), LOCTEXT("DuplicateTooltip", "Create a copy of the selected item(s)."), FSlateIcon(FAppStyle::GetAppStyleSetName(), "Icons.Duplicate") ); // Save Section.AddMenuEntry(FContentBrowserCommands::Get().SaveSelectedAsset, LOCTEXT("SaveAsset", "Save"), LOCTEXT("SaveAssetTooltip", "Saves the item to file."), FSlateIcon(FAppStyle::GetAppStyleSetName(), "Icons.Save") ); // Delete Section.AddMenuEntry(FGenericCommands::Get().Delete, LOCTEXT("Delete", "Delete"), TAttribute::Create(TAttribute::FGetter::CreateSP(this, &FAssetContextMenu::GetDeleteToolTip)) ); } } return true; } void FAssetContextMenu::AddExploreMenuOptions(UToolMenu* Menu) { FToolMenuSection& Section = Menu->AddSection("AssetContextExploreMenuOptions", LOCTEXT("AssetContextExploreMenuOptionsHeading", "Explore")); { // Find in Content Browser Section.AddMenuEntry( FGlobalEditorCommonCommands::Get().FindInContentBrowser, LOCTEXT("ShowInFolderView", "Show in Folder View"), LOCTEXT("ShowInFolderViewTooltip", "Selects the folder that contains this asset in the Content Browser Sources Panel.") ); // Find in Explorer Section.AddMenuEntry( "FindInExplorer", ContentBrowserUtils::GetExploreFolderText(), LOCTEXT("FindInExplorerTooltip", "Finds this asset on disk"), FSlateIcon(FAppStyle::GetAppStyleSetName(), "ContentBrowser.ShowInExplorer"), FUIAction( FExecuteAction::CreateSP(this, &FAssetContextMenu::ExecuteFindInExplorer), FCanExecuteAction::CreateSP(this, &FAssetContextMenu::CanExecuteFindInExplorer) ) ); } } bool FAssetContextMenu::AddPublicStateMenuOptions(UToolMenu* Menu) { if (!bCanExecutePublicAssetToggle && !bCanExecuteBulkSetPublicAsset) { return false; } UContentBrowserDataMenuContext_FileMenu* Context = Menu->FindContext(); { FToolMenuSection& Section = Menu->AddSection("AssetPublicState", LOCTEXT("PublicStateHandling", "Asset State")); if (SelectedFiles.Num() == 1 && bCanExecutePublicAssetToggle) { Section.AddMenuEntry( "PublicAsset", LOCTEXT("PublicAssetToggle", "Public Asset"), LOCTEXT("PublicAssetToggleTooltip", "Sets the asset to be referencable by other Plugins"), FSlateIcon(), FUIAction( FExecuteAction::CreateSP(this, &FAssetContextMenu::ExecutePublicAssetToggle), FCanExecuteAction::CreateSP(this, &FAssetContextMenu::CanExecutePublicAssetToggle), FIsActionChecked::CreateSP(this, &FAssetContextMenu::IsSelectedAssetPublic) ), EUserInterfaceActionType::RadioButton ); Section.AddMenuEntry( "PrivateAsset", LOCTEXT("SetPrivateAsset", "Private Asset"), LOCTEXT("SetAssetPrivateTooltip", "Sets the asset so it can't be referenced by other Plugins"), FSlateIcon(), FUIAction( FExecuteAction::CreateSP(this, &FAssetContextMenu::ExecutePublicAssetToggle), FCanExecuteAction::CreateSP(this, &FAssetContextMenu::CanExecutePublicAssetToggle), FIsActionChecked::CreateSP(this, &FAssetContextMenu::IsSelectedAssetPrivate) ), EUserInterfaceActionType::RadioButton ); } else if (SelectedFiles.Num() > 1 && bCanExecuteBulkSetPublicAsset) { Section.AddMenuEntry( "MarkSelectedAsPublic", LOCTEXT("MarkSelectedAsPublic", "Mark Selected As Public"), LOCTEXT("MarkSelectedAsPublicTooltip", "Sets all selected assets to be publicly available for reference by other plugins"), FSlateIcon(FAppStyle::GetAppStyleSetName(), "ContentBrowser.AssetActions.PublicAssetToggle"), FUIAction( FExecuteAction::CreateSP(this, &FAssetContextMenu::ExecuteBulkSetPublicAsset), FCanExecuteAction::CreateSP(this, &FAssetContextMenu::CanExecuteBulkSetPublicAsset) ) ); Section.AddMenuEntry( "MarkSelectedAsPrivate", LOCTEXT("MarkSelectedAsPrivate", "Mark Selected As Private"), LOCTEXT("MarkSelectedAsPrivateTooltip", "Sets all selected assets to be private and unavailable for reference by other plugins"), FSlateIcon(FAppStyle::GetAppStyleSetName(), "ContentBrowser.AssetActions.PublicAssetToggle"), FUIAction( FExecuteAction::CreateSP(this, &FAssetContextMenu::ExecuteBulkUnsetPublicAsset), FCanExecuteAction::CreateSP(this, &FAssetContextMenu::CanExecuteBulkSetPublicAsset) ) ); } } return true; } bool FAssetContextMenu::AddReferenceMenuOptions(UToolMenu* Menu) { UContentBrowserDataMenuContext_FileMenu* Context = Menu->FindContext(); { FToolMenuSection& Section = Menu->AddSection("AssetContextReferences", LOCTEXT("ReferencesMenuHeading", "References")); Section.AddMenuEntry( "CopyReference", LOCTEXT("CopyReference", "Copy Reference"), LOCTEXT("CopyReferenceTooltip", "Copies reference paths for the selected assets to the clipboard."), FSlateIcon(FAppStyle::GetAppStyleSetName(), "GenericCommands.Copy"), FUIAction( FExecuteAction::CreateSP( this, &FAssetContextMenu::ExecuteCopyReference ) ) ); if (Context->bCanBeModified) { Section.AddMenuEntry( "CopyFilePath", LOCTEXT("CopyFilePath", "Copy File Path"), LOCTEXT("CopyFilePathTooltip", "Copies the file paths on disk for the selected assets to the clipboard."), FSlateIcon(FAppStyle::GetAppStyleSetName(), "GenericCommands.Copy"), FUIAction(FExecuteAction::CreateSP(this, &FAssetContextMenu::ExecuteCopyFilePath)) ); } } return true; } bool FAssetContextMenu::AddAssetTypeMenuOptions(UToolMenu* Menu) { bool bAnyTypeOptions = false; UContentBrowserAssetContextMenuContext* Context = Menu->FindContext(); if (Context && Context->SelectedObjects.Num() > 0) { // Label "GetAssetActions" section FToolMenuSection& Section = Menu->FindOrAddSection("GetAssetActions"); if (Context->CommonAssetTypeActions.IsValid()) { Section.Label = FText::Format(NSLOCTEXT("AssetTools", "AssetSpecificOptionsMenuHeading", "{0} Actions"), Context->CommonAssetTypeActions.Pin()->GetName()); } else if (Context->CommonClass) { Section.Label = FText::Format(NSLOCTEXT("AssetTools", "AssetSpecificOptionsMenuHeading", "{0} Actions"), FText::FromName(Context->CommonClass->GetFName())); } else { Section.Label = FText::Format(NSLOCTEXT("AssetTools", "AssetSpecificOptionsMenuHeading", "{0} Actions"), FText::FromString(TEXT("Asset"))); } bAnyTypeOptions = true; } return bAnyTypeOptions; } bool FAssetContextMenu::AddCollectionMenuOptions(UToolMenu* Menu) { class FManageCollectionsContextMenu { public: static void CreateManageCollectionsSubMenu(UToolMenu* SubMenu, TSharedRef QuickAssetManagement) { FCollectionManagerModule& CollectionManagerModule = FCollectionManagerModule::GetModule(); TArray AvailableCollections; CollectionManagerModule.Get().GetRootCollections(AvailableCollections); CreateManageCollectionsSubMenu(SubMenu, QuickAssetManagement, MoveTemp(AvailableCollections)); } static void CreateManageCollectionsSubMenu(UToolMenu* SubMenu, TSharedRef QuickAssetManagement, TArray AvailableCollections) { FCollectionManagerModule& CollectionManagerModule = FCollectionManagerModule::GetModule(); AvailableCollections.Sort([](const FCollectionNameType& One, const FCollectionNameType& Two) -> bool { return One.Name.LexicalLess(Two.Name); }); FToolMenuSection& Section = SubMenu->AddSection("Section"); 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) { Section.AddSubMenu( NAME_None, FText::FromName(AvailableCollection.Name), FText::GetEmpty(), FNewToolMenuDelegate::CreateStatic(&FManageCollectionsContextMenu::CreateManageCollectionsSubMenu, QuickAssetManagement, AvailableChildCollections), FUIAction( FExecuteAction::CreateStatic(&FManageCollectionsContextMenu::OnCollectionClicked, QuickAssetManagement, AvailableCollection), FCanExecuteAction::CreateStatic(&FManageCollectionsContextMenu::IsCollectionEnabled, QuickAssetManagement, AvailableCollection), FGetActionCheckState::CreateStatic(&FManageCollectionsContextMenu::GetCollectionCheckState, QuickAssetManagement, AvailableCollection) ), EUserInterfaceActionType::ToggleButton, false, FSlateIcon(FAppStyle::GetAppStyleSetName(), ECollectionShareType::GetIconStyleName(AvailableCollection.Type)) ); } else { Section.AddMenuEntry( NAME_None, FText::FromName(AvailableCollection.Name), FText::GetEmpty(), FSlateIcon(FAppStyle::GetAppStyleSetName(), ECollectionShareType::GetIconStyleName(AvailableCollection.Type)), FUIAction( FExecuteAction::CreateStatic(&FManageCollectionsContextMenu::OnCollectionClicked, QuickAssetManagement, AvailableCollection), FCanExecuteAction::CreateStatic(&FManageCollectionsContextMenu::IsCollectionEnabled, QuickAssetManagement, AvailableCollection), FGetActionCheckState::CreateStatic(&FManageCollectionsContextMenu::GetCollectionCheckState, QuickAssetManagement, AvailableCollection) ), 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(); FToolMenuSection& Section = Menu->AddSection("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 = MakeShared(); TArray SelectedItemCollectionIds; for (const FContentBrowserItem& SelectedItem : SelectedFiles) { FName ItemCollectionId; if (SelectedItem.TryGetCollectionId(ItemCollectionId)) { SelectedItemCollectionIds.Add(ItemCollectionId); } } QuickAssetManagement->SetCurrentAssetPaths(SelectedItemCollectionIds); Section.AddSubMenu( "ManageCollections", LOCTEXT("ManageCollections", "Manage Collections"), FText::Format(LOCTEXT("ManageCollections_ToolTip", "Manage the collections that the selected {0}|plural(one=item belongs, other=items belong) to."), SelectedFiles.Num()), FNewToolMenuDelegate::CreateStatic(&FManageCollectionsContextMenu::CreateManageCollectionsSubMenu, QuickAssetManagement), false, // default value FSlateIcon(FAppStyle::GetAppStyleSetName(), "ContentBrowser.Collections") ); bHasAddedItems = true; } // "Remove from collection" (only display option if exactly one collection is selected) if ( SourcesData.Collections.Num() == 1 && !SourcesData.IsDynamicCollection() ) { Section.AddMenuEntry( "RemoveFromCollection", FText::Format(LOCTEXT("RemoveFromCollectionFmt", "Remove From {0}"), FText::FromName(SourcesData.Collections[0].Name)), LOCTEXT("RemoveFromCollection_ToolTip", "Removes the selected item from the current collection."), FSlateIcon(), FUIAction( FExecuteAction::CreateSP( this, &FAssetContextMenu::ExecuteRemoveFromCollection ), FCanExecuteAction::CreateSP( this, &FAssetContextMenu::CanExecuteRemoveFromCollection ) ) ); bHasAddedItems = true; } return bHasAddedItems; } void FAssetContextMenu::ExecuteSyncToAssetTree() { // Copy this as the sync may adjust our selected assets array const TArray SelectedFilesCopy = SelectedFiles; OnShowInPathsViewRequested.ExecuteIfBound(SelectedFilesCopy); } void FAssetContextMenu::ExecuteFindInExplorer() { ContentBrowserUtils::ExploreFolders(SelectedFiles, AssetView.Pin().ToSharedRef()); } bool FAssetContextMenu::CanExecuteEditItems() const { bool bCanEdit = false; for (const FContentBrowserItem& SelectedItem : SelectedFiles) { bCanEdit |= SelectedItem.CanEdit(); } return bCanEdit; } void FAssetContextMenu::ExecuteEditItems() { if (SelectedFiles.Num() > 0) { OnEditRequested.ExecuteIfBound(SelectedFiles); } } void FAssetContextMenu::ExecuteSaveAsset() { const EContentBrowserItemSaveFlags SaveFlags = EContentBrowserItemSaveFlags::SaveOnlyIfLoaded; // Batch these by their data sources TMap> SourcesAndItems; for (const FContentBrowserItem& SelectedItem : SelectedFiles) { FContentBrowserItem::FItemDataArrayView ItemDataArray = SelectedItem.GetInternalItems(); for (const FContentBrowserItemData& ItemData : ItemDataArray) { if (UContentBrowserDataSource* ItemDataSource = ItemData.GetOwnerDataSource()) { FText SaveErrorMsg; if (ItemDataSource->CanSaveItem(ItemData, SaveFlags, &SaveErrorMsg)) { TArray& ItemsForSource = SourcesAndItems.FindOrAdd(ItemDataSource); ItemsForSource.Add(ItemData); } else { AssetViewUtils::ShowErrorNotifcation(SaveErrorMsg); } } } } // Execute the operation now for (const auto& SourceAndItemsPair : SourcesAndItems) { SourceAndItemsPair.Key->BulkSaveItems(SourceAndItemsPair.Value, SaveFlags); } } void FAssetContextMenu::ExecuteDuplicate() { if (SelectedFiles.Num() > 0) { OnDuplicateRequested.ExecuteIfBound(SelectedFiles); } } void FAssetContextMenu::ExecuteRename(EContentBrowserViewContext ViewContext) { if (SelectedItems.Num() == 1) { OnRenameRequested.ExecuteIfBound(SelectedItems[0], ViewContext); } } void FAssetContextMenu::ExecuteDelete() { // Batch these by their data sources TMap> SourcesAndItems; for (const FContentBrowserItem& SelectedItem : SelectedFiles) { FContentBrowserItem::FItemDataArrayView ItemDataArray = SelectedItem.GetInternalItems(); for (const FContentBrowserItemData& ItemData : ItemDataArray) { if (UContentBrowserDataSource* ItemDataSource = ItemData.GetOwnerDataSource()) { FText DeleteErrorMsg; if (ItemDataSource->CanDeleteItem(ItemData, &DeleteErrorMsg)) { TArray& ItemsForSource = SourcesAndItems.FindOrAdd(ItemDataSource); ItemsForSource.Add(ItemData); } else { AssetViewUtils::ShowErrorNotifcation(DeleteErrorMsg); } } } } // Execute the operation now for (const auto& SourceAndItemsPair : SourcesAndItems) { SourceAndItemsPair.Key->BulkDeleteItems(SourceAndItemsPair.Value); } // If we had any folders selected, ask the user whether they want to delete them // as it can be slow to build the deletion dialog on an accidental click if (SelectedFolders.Num() > 0) { FText Prompt; if (SelectedFolders.Num() == 1) { Prompt = FText::Format(LOCTEXT("FolderDeleteConfirm_Single", "Delete folder '{0}'?"), SelectedFolders[0].GetDisplayName()); } else { Prompt = FText::Format(LOCTEXT("FolderDeleteConfirm_Multiple", "Delete {0} folders?"), 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) ); } } FReply FAssetContextMenu::ExecuteDeleteFolderConfirmed() { // Batch these by their data sources TMap> SourcesAndItems; for (const FContentBrowserItem& SelectedItem : SelectedFolders) { FContentBrowserItem::FItemDataArrayView ItemDataArray = SelectedItem.GetInternalItems(); for (const FContentBrowserItemData& ItemData : ItemDataArray) { if (UContentBrowserDataSource* ItemDataSource = ItemData.GetOwnerDataSource()) { FText DeleteErrorMsg; if (ItemDataSource->CanDeleteItem(ItemData, &DeleteErrorMsg)) { TArray& ItemsForSource = SourcesAndItems.FindOrAdd(ItemDataSource); ItemsForSource.Add(ItemData); } else { AssetViewUtils::ShowErrorNotifcation(DeleteErrorMsg); } } } } // Execute the operation now for (const auto& SourceAndItemsPair : SourcesAndItems) { SourceAndItemsPair.Key->BulkDeleteItems(SourceAndItemsPair.Value); } return FReply::Handled(); } void FAssetContextMenu::ExecutePublicAssetToggle() { // Toggle selected files public asset flag if (ensure(SelectedFiles.Num() == 1)) { FAssetData ItemAssetData; if (SelectedFiles[0].Legacy_TryGetAssetData(ItemAssetData)) { UPackage* ItemAssetPackage = ItemAssetData.GetPackage(); if (!ItemAssetPackage) { return; } ItemAssetPackage->SetIsExternallyReferenceable(!ItemAssetPackage->IsExternallyReferenceable()); ItemAssetPackage->Modify(); OnAssetViewRefreshRequested.ExecuteIfBound(); } } } bool FAssetContextMenu::CanExecutePublicAssetToggle() { return bCanExecutePublicAssetToggle; } void FAssetContextMenu::ExecuteBulkSetPublicAsset() { for (const FContentBrowserItem& SelectedItem : SelectedFiles) { FAssetData ItemAssetData; if (SelectedItem.Legacy_TryGetAssetData(ItemAssetData)) { UPackage* ItemAssetPackage = ItemAssetData.GetPackage(); if (!ItemAssetPackage) { continue; } if (!ItemAssetPackage->IsExternallyReferenceable()) { ItemAssetPackage->SetIsExternallyReferenceable(true); ItemAssetPackage->Modify(); } } } OnAssetViewRefreshRequested.ExecuteIfBound(); } void FAssetContextMenu::ExecuteBulkUnsetPublicAsset() { for (const FContentBrowserItem& SelectedItem : SelectedFiles) { FAssetData ItemAssetData; if (SelectedItem.Legacy_TryGetAssetData(ItemAssetData)) { UPackage* ItemAssetPackage = ItemAssetData.GetPackage(); if (!ItemAssetPackage) { continue; } if (ItemAssetPackage->IsExternallyReferenceable()) { ItemAssetPackage->SetIsExternallyReferenceable(false); ItemAssetPackage->Modify(); } } } OnAssetViewRefreshRequested.ExecuteIfBound(); } bool FAssetContextMenu::CanExecuteBulkSetPublicAsset() { return bCanExecuteBulkSetPublicAsset; } bool FAssetContextMenu::IsSelectedAssetPublic() { if (ensure(SelectedFiles.Num() == 1)) { FAssetData ItemAssetData; if (SelectedFiles[0].Legacy_TryGetAssetData(ItemAssetData)) { UPackage* ItemAssetPackage = ItemAssetData.GetPackage(); if (ItemAssetPackage) { return ItemAssetPackage->IsExternallyReferenceable(); } } } return true; } bool FAssetContextMenu::IsSelectedAssetPrivate() { return !IsSelectedAssetPublic(); } void FAssetContextMenu::ExecuteCopyReference() { if (SelectedFiles.Num() > 0) { ContentBrowserUtils::CopyItemReferencesToClipboard(SelectedFiles); } } void FAssetContextMenu::ExecuteCopyFilePath() { if (SelectedFiles.Num() > 0) { ContentBrowserUtils::CopyFilePathsToClipboard(SelectedFiles); } } void FAssetContextMenu::ExecuteRemoveFromCollection() { if ( ensure(SourcesData.Collections.Num() == 1) ) { TArray SelectedItemCollectionIds; for (const FContentBrowserItem& SelectedItem : SelectedFiles) { FName ItemCollectionId; if (SelectedItem.TryGetCollectionId(ItemCollectionId)) { SelectedItemCollectionIds.Add(ItemCollectionId); } } if ( SelectedItemCollectionIds.Num() > 0 ) { FCollectionManagerModule& CollectionManagerModule = FCollectionManagerModule::GetModule(); const FCollectionNameType& Collection = SourcesData.Collections[0]; CollectionManagerModule.Get().RemoveFromCollection(Collection.Name, Collection.Type, SelectedItemCollectionIds); OnAssetViewRefreshRequested.ExecuteIfBound(); } } } bool FAssetContextMenu::CanExecuteSyncToAssetTree() const { return SelectedFiles.Num() > 0; } bool FAssetContextMenu::CanExecuteFindInExplorer() const { return bCanExecuteFindInExplorer; } bool FAssetContextMenu::CanExecuteRemoveFromCollection() const { return SourcesData.Collections.Num() == 1 && !SourcesData.IsDynamicCollection(); } bool FAssetContextMenu::CanExecuteDuplicate() const { bool bCanDuplicate = false; for (const FContentBrowserItem& SelectedItem : SelectedFiles) { bCanDuplicate |= SelectedItem.CanDuplicate(); } return bCanDuplicate; } bool FAssetContextMenu::CanExecuteRename() const { return ContentBrowserUtils::CanRenameFromAssetView(AssetView); } bool FAssetContextMenu::CanExecuteDelete() const { return ContentBrowserUtils::CanDeleteFromAssetView(AssetView); } FText FAssetContextMenu::GetDeleteToolTip() const { FText ErrorMessage; if (!ContentBrowserUtils::CanDeleteFromAssetView(AssetView, &ErrorMessage) && !ErrorMessage.IsEmpty()) { return ErrorMessage; } return LOCTEXT("DeleteTooltip", "Delete the selected items."); } bool FAssetContextMenu::CanExecuteSaveAsset() const { bool bCanSave = false; for (const FContentBrowserItem& SelectedItem : SelectedFiles) { bCanSave |= SelectedItem.CanSave(EContentBrowserItemSaveFlags::SaveOnlyIfLoaded); } return bCanSave; } void FAssetContextMenu::CacheCanExecuteVars() { bCanExecuteFindInExplorer = false; bCanExecutePublicAssetToggle = false; bCanExecuteBulkSetPublicAsset = true; // Selection must contain at least one file that has exists on disk for (const FContentBrowserItem& SelectedItem : SelectedFiles) { FString ItemFilename; if (!bCanExecuteFindInExplorer && SelectedItem.GetItemPhysicalPath(ItemFilename) && FPaths::FileExists(ItemFilename)) { bCanExecuteFindInExplorer = true; } if(SelectedItem.CanEdit() && FContentBrowserSingleton::Get().IsShowingPrivateContent(FPathViews::GetPath(FNameBuilder(SelectedItem.GetVirtualPath())))) { bCanExecutePublicAssetToggle = true; } else { bCanExecuteBulkSetPublicAsset = false; } } } #undef LOCTEXT_NAMESPACE