// Copyright Epic Games, Inc. All Rights Reserved. #include "ContentBrowserUtils.h" #include "ContentBrowserSingleton.h" #include "HAL/IConsoleManager.h" #include "Misc/MessageDialog.h" #include "HAL/FileManager.h" #include "HAL/PlatformApplicationMisc.h" #include "Misc/Paths.h" #include "Misc/ConfigCacheIni.h" #include "Misc/FeedbackContext.h" #include "Misc/ScopedSlowTask.h" #include "Misc/App.h" #include "Misc/FileHelper.h" #include "Modules/ModuleManager.h" #include "Widgets/DeclarativeSyntaxSupport.h" #include "Widgets/SCompoundWidget.h" #include "Widgets/SBoxPanel.h" #include "Layout/WidgetPath.h" #include "SlateOptMacros.h" #include "Framework/Application/MenuStack.h" #include "Framework/Application/SlateApplication.h" #include "Widgets/Layout/SBorder.h" #include "Widgets/Images/SImage.h" #include "Widgets/Text/STextBlock.h" #include "Widgets/Layout/SUniformGridPanel.h" #include "Widgets/Input/SButton.h" #include "Styling/AppStyle.h" #include "UnrealClient.h" #include "Engine/World.h" #include "Settings/ContentBrowserSettings.h" #include "Settings/EditorExperimentalSettings.h" #include "SourceControlOperations.h" #include "ISourceControlModule.h" #include "SourceControlHelpers.h" #include "FileHelpers.h" #include "AssetRegistry/ARFilter.h" #include "AssetRegistry/AssetRegistryModule.h" #include "IAssetTools.h" #include "AssetToolsModule.h" #include "Settings/EditorExperimentalSettings.h" #include "PackagesDialog.h" #include "PackageTools.h" #include "ObjectTools.h" #include "ImageUtils.h" #include "Logging/MessageLog.h" #include "Misc/EngineBuildSettings.h" #include "Framework/Notifications/NotificationManager.h" #include "Widgets/Notifications/SNotificationList.h" #include "Interfaces/IPluginManager.h" #include "SAssetView.h" #include "SPathView.h" #include "ContentBrowserLog.h" #include "Subsystems/AssetEditorSubsystem.h" #include "Editor.h" #include "IContentBrowserDataModule.h" #include "ContentBrowserDataSource.h" #include "ContentBrowserDataSubsystem.h" #define LOCTEXT_NAMESPACE "ContentBrowser" namespace ContentBrowserUtils { /** Converts a virtual path such as /All/Plugins -> /Plugins or /All/Game -> /Game */ FString ConvertVirtualPathToInvariantPathString(const FString& VirtualPath) { FName ConvertedPath; IContentBrowserDataModule::Get().GetSubsystem()->TryConvertVirtualPath(FName(VirtualPath), ConvertedPath); return ConvertedPath.ToString(); } } class SContentBrowserPopup : public SCompoundWidget { public: SLATE_BEGIN_ARGS( SContentBrowserPopup ){} SLATE_ATTRIBUTE( FText, Message ) SLATE_END_ARGS() /** Constructs this widget with InArgs */ BEGIN_SLATE_FUNCTION_BUILD_OPTIMIZATION void Construct( const FArguments& InArgs ) { ChildSlot [ SNew(SBorder) .BorderImage(FAppStyle::GetBrush("Menu.Background")) .Padding(10) .OnMouseButtonDown(this, &SContentBrowserPopup::OnBorderClicked) .BorderBackgroundColor(this, &SContentBrowserPopup::GetBorderBackgroundColor) [ SNew(SHorizontalBox) +SHorizontalBox::Slot() .AutoWidth() .VAlign(VAlign_Center) .Padding(0, 0, 4, 0) [ SNew(SImage) .Image( FAppStyle::GetBrush("ContentBrowser.PopupMessageIcon") ) ] +SHorizontalBox::Slot() .AutoWidth() .VAlign(VAlign_Center) [ SNew(STextBlock) .Text(InArgs._Message) .WrapTextAt(450) ] ] ]; } END_SLATE_FUNCTION_BUILD_OPTIMIZATION static void DisplayMessage( const FText& Message, const FSlateRect& ScreenAnchor, TSharedRef ParentContent ) { TSharedRef PopupContent = SNew(SContentBrowserPopup) .Message(Message); const FVector2D ScreenLocation = FVector2D(ScreenAnchor.Left, ScreenAnchor.Top); const bool bFocusImmediately = true; const FVector2D SummonLocationSize = ScreenAnchor.GetSize(); TSharedPtr Menu = FSlateApplication::Get().PushMenu( ParentContent, FWidgetPath(), PopupContent, ScreenLocation, FPopupTransitionEffect( FPopupTransitionEffect::TopMenu ), bFocusImmediately, SummonLocationSize); PopupContent->SetMenu(Menu); } private: void SetMenu(const TSharedPtr& InMenu) { Menu = InMenu; } FReply OnBorderClicked(const FGeometry& Geometry, const FPointerEvent& MouseEvent) { if (Menu.IsValid()) { Menu.Pin()->Dismiss(); } return FReply::Handled(); } FSlateColor GetBorderBackgroundColor() const { return IsHovered() ? FLinearColor(0.5, 0.5, 0.5, 1) : FLinearColor::White; } private: TWeakPtr Menu; }; /** A miniture confirmation popup for quick yes/no questions */ class SContentBrowserConfirmPopup : public SCompoundWidget { public: SLATE_BEGIN_ARGS( SContentBrowserConfirmPopup ) {} /** The text to display */ SLATE_ARGUMENT(FText, Prompt) /** The Yes Button to display */ SLATE_ARGUMENT(FText, YesText) /** The No Button to display */ SLATE_ARGUMENT(FText, NoText) /** Invoked when yes is clicked */ SLATE_EVENT(FOnClicked, OnYesClicked) /** Invoked when no is clicked */ SLATE_EVENT(FOnClicked, OnNoClicked) SLATE_END_ARGS() BEGIN_SLATE_FUNCTION_BUILD_OPTIMIZATION void Construct( const FArguments& InArgs ) { OnYesClicked = InArgs._OnYesClicked; OnNoClicked = InArgs._OnNoClicked; ChildSlot [ SNew(SBorder) . BorderImage(FAppStyle::GetBrush("Menu.Background")) . Padding(10) [ SNew(SVerticalBox) +SVerticalBox::Slot() .AutoHeight() .Padding(0, 0, 0, 5) .HAlign(HAlign_Center) [ SNew(STextBlock) .Text(InArgs._Prompt) ] +SVerticalBox::Slot() .AutoHeight() .HAlign(HAlign_Center) [ SNew(SUniformGridPanel) .SlotPadding(3) + SUniformGridPanel::Slot(0, 0) .HAlign(HAlign_Fill) [ SNew(SButton) .HAlign(HAlign_Center) .Text(InArgs._YesText) .OnClicked( this, &SContentBrowserConfirmPopup::YesClicked ) ] + SUniformGridPanel::Slot(1, 0) .HAlign(HAlign_Fill) [ SNew(SButton) .HAlign(HAlign_Center) .Text(InArgs._NoText) .OnClicked( this, &SContentBrowserConfirmPopup::NoClicked ) ] ] ] ]; } END_SLATE_FUNCTION_BUILD_OPTIMIZATION /** Opens the popup using the specified component as its parent */ void OpenPopup(const TSharedRef& ParentContent) { // Show dialog to confirm the delete Menu = FSlateApplication::Get().PushMenu( ParentContent, FWidgetPath(), SharedThis(this), FSlateApplication::Get().GetCursorPos(), FPopupTransitionEffect( FPopupTransitionEffect::TopMenu ) ); } private: /** The yes button was clicked */ FReply YesClicked() { if ( OnYesClicked.IsBound() ) { OnYesClicked.Execute(); } if (Menu.IsValid()) { Menu.Pin()->Dismiss(); } return FReply::Handled(); } /** The no button was clicked */ FReply NoClicked() { if ( OnNoClicked.IsBound() ) { OnNoClicked.Execute(); } if (Menu.IsValid()) { Menu.Pin()->Dismiss(); } return FReply::Handled(); } /** The IMenu prepresenting this popup */ TWeakPtr Menu; /** Delegates for button clicks */ FOnClicked OnYesClicked; FOnClicked OnNoClicked; }; void ContentBrowserUtils::DisplayMessage(const FText& Message, const FSlateRect& ScreenAnchor, const TSharedRef& ParentContent) { SContentBrowserPopup::DisplayMessage(Message, ScreenAnchor, ParentContent); } void ContentBrowserUtils::DisplayConfirmationPopup(const FText& Message, const FText& YesString, const FText& NoString, const TSharedRef& ParentContent, const FOnClicked& OnYesClicked, const FOnClicked& OnNoClicked) { TSharedRef Popup = SNew(SContentBrowserConfirmPopup) .Prompt(Message) .YesText(YesString) .NoText(NoString) .OnYesClicked( OnYesClicked ) .OnNoClicked( OnNoClicked ); Popup->OpenPopup(ParentContent); } void ContentBrowserUtils::CopyItemReferencesToClipboard(const TArray& ItemsToCopy) { TArray SortedItems = ItemsToCopy; SortedItems.Sort([](const FContentBrowserItem& One, const FContentBrowserItem& Two) { return One.GetVirtualPath().Compare(Two.GetVirtualPath()) < 0; }); FString ClipboardText; for (const FContentBrowserItem& Item : SortedItems) { Item.AppendItemReference(ClipboardText); } FPlatformApplicationMisc::ClipboardCopy(*ClipboardText); } void ContentBrowserUtils::CopyFilePathsToClipboard(const TArray& ItemsToCopy) { TArray SortedItems = ItemsToCopy; SortedItems.Sort([](const FContentBrowserItem& One, const FContentBrowserItem& Two) { return One.GetVirtualPath().Compare(Two.GetVirtualPath()) < 0; }); FString ClipboardText; for (const FContentBrowserItem& Item : SortedItems) { if (ClipboardText.Len() > 0) { ClipboardText += LINE_TERMINATOR; } FString ItemFilename; if (Item.GetItemPhysicalPath(ItemFilename) && FPaths::FileExists(ItemFilename)) { ClipboardText += FPaths::ConvertRelativePathToFull(ItemFilename); } else { // Add a message for when a user tries to copy the path to a file that doesn't exist on disk of the form // : No file on disk ClipboardText += FString::Printf(TEXT("%s: No file on disk"), *Item.GetDisplayName().ToString()); } } FPlatformApplicationMisc::ClipboardCopy(*ClipboardText); } bool ContentBrowserUtils::IsItemDeveloperContent(const FContentBrowserItem& InItem) { const FContentBrowserItemDataAttributeValue IsDeveloperAttributeValue = InItem.GetItemAttribute(ContentBrowserItemAttributes::ItemIsDeveloperContent); return IsDeveloperAttributeValue.IsValid() && IsDeveloperAttributeValue.GetValue(); } bool ContentBrowserUtils::IsItemLocalizedContent(const FContentBrowserItem& InItem) { const FContentBrowserItemDataAttributeValue IsLocalizedAttributeValue = InItem.GetItemAttribute(ContentBrowserItemAttributes::ItemIsLocalizedContent); return IsLocalizedAttributeValue.IsValid() && IsLocalizedAttributeValue.GetValue(); } bool ContentBrowserUtils::IsItemEngineContent(const FContentBrowserItem& InItem) { const FContentBrowserItemDataAttributeValue IsEngineAttributeValue = InItem.GetItemAttribute(ContentBrowserItemAttributes::ItemIsEngineContent); return IsEngineAttributeValue.IsValid() && IsEngineAttributeValue.GetValue(); } bool ContentBrowserUtils::IsItemProjectContent(const FContentBrowserItem& InItem) { const FContentBrowserItemDataAttributeValue IsProjectAttributeValue = InItem.GetItemAttribute(ContentBrowserItemAttributes::ItemIsProjectContent); return IsProjectAttributeValue.IsValid() && IsProjectAttributeValue.GetValue(); } bool ContentBrowserUtils::IsItemPluginContent(const FContentBrowserItem& InItem) { const FContentBrowserItemDataAttributeValue IsPluginAttributeValue = InItem.GetItemAttribute(ContentBrowserItemAttributes::ItemIsPluginContent); return IsPluginAttributeValue.IsValid() && IsPluginAttributeValue.GetValue(); } bool ContentBrowserUtils::IsCollectionPath(const FString& InPath, FName* OutCollectionName, ECollectionShareType::Type* OutCollectionShareType) { static const FString CollectionsRootPrefix = TEXT("/Collections"); if (InPath.StartsWith(CollectionsRootPrefix)) { TArray PathParts; InPath.ParseIntoArray(PathParts, TEXT("/")); check(PathParts.Num() > 2); // The second part of the path is the share type name if (OutCollectionShareType) { *OutCollectionShareType = ECollectionShareType::FromString(*PathParts[1]); } // The third part of the path is the collection name if (OutCollectionName) { *OutCollectionName = FName(*PathParts[2]); } return true; } return false; } void ContentBrowserUtils::CountPathTypes(const TArray& InPaths, int32& OutNumAssetPaths, int32& OutNumClassPaths) { static const FString ClassesRootPrefix = TEXT("/Classes_"); OutNumAssetPaths = 0; OutNumClassPaths = 0; for(const FString& Path : InPaths) { if(Path.StartsWith(ClassesRootPrefix)) { ++OutNumClassPaths; } else { ++OutNumAssetPaths; } } } void ContentBrowserUtils::CountPathTypes(const TArray& InPaths, int32& OutNumAssetPaths, int32& OutNumClassPaths) { static const FString ClassesRootPrefix = TEXT("/Classes_"); OutNumAssetPaths = 0; OutNumClassPaths = 0; for(const FName& Path : InPaths) { if(Path.ToString().StartsWith(ClassesRootPrefix)) { ++OutNumClassPaths; } else { ++OutNumAssetPaths; } } } void ContentBrowserUtils::CountItemTypes(const TArray& InItems, int32& OutNumAssetItems, int32& OutNumClassItems) { OutNumAssetItems = 0; OutNumClassItems = 0; const FTopLevelAssetPath ClassPath(TEXT("/Script/CoreUObject"), TEXT("Class")); for(const FAssetData& Item : InItems) { if(Item.AssetClassPath == ClassPath) { ++OutNumClassItems; } else { ++OutNumAssetItems; } } } FText ContentBrowserUtils::GetExploreFolderText() { FFormatNamedArguments Args; Args.Add(TEXT("FileManagerName"), FPlatformMisc::GetFileManagerName()); return FText::Format(NSLOCTEXT("GenericPlatform", "ShowInFileManager", "Show in {FileManagerName}"), Args); } void ContentBrowserUtils::ExploreFolders(const TArray& InItems, const TSharedRef& InParentContent) { TArray ExploreItems; for (const FContentBrowserItem& SelectedItem : InItems) { FString ItemFilename; if (SelectedItem.GetItemPhysicalPath(ItemFilename)) { const bool bExists = SelectedItem.IsFile() ? FPaths::FileExists(ItemFilename) : FPaths::DirectoryExists(ItemFilename); if (bExists) { ExploreItems.Add(IFileManager::Get().ConvertToAbsolutePathForExternalAppForRead(*ItemFilename)); } } } const int32 BatchSize = 10; const FText FileManagerName = FPlatformMisc::GetFileManagerName(); const bool bHasMultipleBatches = ExploreItems.Num() > BatchSize; for (int32 i = 0; i < ExploreItems.Num(); ++i) { bool bIsBatchBoundary = (i % BatchSize) == 0; if (bHasMultipleBatches && bIsBatchBoundary) { int32 RemainingCount = ExploreItems.Num() - i; int32 NextCount = FMath::Min(BatchSize, RemainingCount); FText Prompt = FText::Format(LOCTEXT("ExecuteExploreConfirm", "Show {0} {0}|plural(one=item,other=items) in {1}?\nThere {2}|plural(one=is,other=are) {2} remaining."), NextCount, FileManagerName, RemainingCount); if (FMessageDialog::Open(EAppMsgType::YesNo, Prompt) != EAppReturnType::Yes) { return; } } FPlatformProcess::ExploreFolder(*ExploreItems[i]); } } bool ContentBrowserUtils::CanExploreFolders(const TArray& InItems) { for (const FContentBrowserItem& SelectedItem : InItems) { FString ItemFilename; if (SelectedItem.GetItemPhysicalPath(ItemFilename)) { const bool bExists = SelectedItem.IsFile() ? FPaths::FileExists(ItemFilename) : FPaths::DirectoryExists(ItemFilename); if (bExists) { return true; } } } return false; } template void ConvertLegacySelectionToVirtualPathsImpl(TArrayView InAssets, TArrayView InFolders, const bool InUseFolderPaths, OutputContainerType& OutVirtualPaths) { OutVirtualPaths.Reset(); if (InAssets.Num() == 0 && InFolders.Num() == 0) { return; } UContentBrowserDataSubsystem* ContentBrowserData = IContentBrowserDataModule::Get().GetSubsystem(); auto AppendVirtualPath = [&OutVirtualPaths](FName InPath) { OutVirtualPaths.Add(InPath); return true; }; for (const FAssetData& Asset : InAssets) { ContentBrowserData->Legacy_TryConvertAssetDataToVirtualPaths(Asset, InUseFolderPaths, AppendVirtualPath); } for (const FString& Folder : InFolders) { ContentBrowserData->Legacy_TryConvertPackagePathToVirtualPaths(*Folder, AppendVirtualPath); } } void ContentBrowserUtils::ConvertLegacySelectionToVirtualPaths(TArrayView InAssets, TArrayView InFolders, const bool InUseFolderPaths, TArray& OutVirtualPaths) { ConvertLegacySelectionToVirtualPathsImpl(InAssets, InFolders, InUseFolderPaths, OutVirtualPaths); } void ContentBrowserUtils::ConvertLegacySelectionToVirtualPaths(TArrayView InAssets, TArrayView InFolders, const bool InUseFolderPaths, TSet& OutVirtualPaths) { ConvertLegacySelectionToVirtualPathsImpl(InAssets, InFolders, InUseFolderPaths, OutVirtualPaths); } void ContentBrowserUtils::AppendAssetFilterToContentBrowserFilter(const FARFilter& InAssetFilter, const TSharedPtr& InAssetClassPermissionList, const TSharedPtr& InFolderPermissionList, FContentBrowserDataFilter& OutDataFilter) { if (InAssetFilter.ObjectPaths.Num() > 0 || InAssetFilter.TagsAndValues.Num() > 0 || InAssetFilter.bIncludeOnlyOnDiskAssets) { FContentBrowserDataObjectFilter& ObjectFilter = OutDataFilter.ExtraFilters.FindOrAddFilter(); ObjectFilter.ObjectNamesToInclude = InAssetFilter.ObjectPaths; ObjectFilter.TagsAndValuesToInclude = InAssetFilter.TagsAndValues; ObjectFilter.bOnDiskObjectsOnly = InAssetFilter.bIncludeOnlyOnDiskAssets; } if (InAssetFilter.PackageNames.Num() > 0 || InAssetFilter.PackagePaths.Num() > 0 || (InFolderPermissionList && InFolderPermissionList->HasFiltering())) { FContentBrowserDataPackageFilter& PackageFilter = OutDataFilter.ExtraFilters.FindOrAddFilter(); PackageFilter.PackageNamesToInclude = InAssetFilter.PackageNames; PackageFilter.PackagePathsToInclude = InAssetFilter.PackagePaths; PackageFilter.bRecursivePackagePathsToInclude = InAssetFilter.bRecursivePaths; PackageFilter.PathPermissionList = InFolderPermissionList; } if (InAssetFilter.ClassPaths.Num() > 0 || (InAssetClassPermissionList && InAssetClassPermissionList->HasFiltering())) { FContentBrowserDataClassFilter& ClassFilter = OutDataFilter.ExtraFilters.FindOrAddFilter(); for (FTopLevelAssetPath ClassPathName : InAssetFilter.ClassPaths) { ClassFilter.ClassNamesToInclude.Add(ClassPathName.ToString()); } ClassFilter.bRecursiveClassNamesToInclude = InAssetFilter.bRecursiveClasses; if (InAssetFilter.bRecursiveClasses) { for (FTopLevelAssetPath ClassPathName : InAssetFilter.RecursiveClassPathsExclusionSet) { ClassFilter.ClassNamesToExclude.Add(ClassPathName.ToString()); } ClassFilter.bRecursiveClassNamesToExclude = false; } ClassFilter.ClassPermissionList = InAssetClassPermissionList; } } TSharedPtr ContentBrowserUtils::GetCombinedFolderPermissionList(const TSharedPtr& FolderPermissionList, const TSharedPtr& WritableFolderPermissionList) { TSharedPtr CombinedFolderPermissionList; const bool bHidingFolders = FolderPermissionList && FolderPermissionList->HasFiltering(); const bool bHidingReadOnlyFolders = WritableFolderPermissionList && WritableFolderPermissionList->HasFiltering(); if (bHidingFolders || bHidingReadOnlyFolders) { CombinedFolderPermissionList = MakeShared(); if (bHidingReadOnlyFolders && bHidingFolders) { FPathPermissionList IntersectedFilter = FolderPermissionList->CombinePathFilters(*WritableFolderPermissionList.Get()); CombinedFolderPermissionList->Append(IntersectedFilter); } else if (bHidingReadOnlyFolders) { CombinedFolderPermissionList->Append(*WritableFolderPermissionList); } else if (bHidingFolders) { CombinedFolderPermissionList->Append(*FolderPermissionList); } } return CombinedFolderPermissionList; } bool ContentBrowserUtils::CanDeleteFromAssetView(TWeakPtr AssetView, FText* OutErrorMsg) { if (TSharedPtr AssetViewPin = AssetView.Pin()) { const TArray SelectedItems = AssetViewPin->GetSelectedItems(); bool bCanDelete = false; for (const FContentBrowserItem& SelectedItem : SelectedItems) { bCanDelete |= SelectedItem.CanDelete(OutErrorMsg); } return bCanDelete; } return false; } bool ContentBrowserUtils::CanRenameFromAssetView(TWeakPtr AssetView, FText* OutErrorMsg) { if (TSharedPtr AssetViewPin = AssetView.Pin()) { const TArray SelectedItems = AssetViewPin->GetSelectedItems(); return SelectedItems.Num() == 1 && SelectedItems[0].CanRename(nullptr, OutErrorMsg) && !AssetViewPin->IsThumbnailEditMode(); } return false; } bool ContentBrowserUtils::CanDeleteFromPathView(TWeakPtr PathView, FText* OutErrorMsg) { if (TSharedPtr PathViewPin = PathView.Pin()) { const TArray SelectedItems = PathViewPin->GetSelectedFolderItems(); bool bCanDelete = false; for (const FContentBrowserItem& SelectedItem : SelectedItems) { bCanDelete |= SelectedItem.CanDelete(OutErrorMsg); } return bCanDelete; } return false; } bool ContentBrowserUtils::CanRenameFromPathView(TWeakPtr PathView, FText* OutErrorMsg) { if (TSharedPtr PathViewPin = PathView.Pin()) { const TArray SelectedItems = PathViewPin->GetSelectedFolderItems(); return SelectedItems.Num() == 1 && SelectedItems[0].CanRename(nullptr, OutErrorMsg); } return false; } bool ContentBrowserUtils::IsFavoriteFolder(const FString& FolderPath) { return FContentBrowserSingleton::Get().FavoriteFolderPaths.Contains(ConvertVirtualPathToInvariantPathString(FolderPath)); } void ContentBrowserUtils::AddFavoriteFolder(const FString& FolderPath, bool bFlushConfig /*= true*/) { FContentBrowserSingleton::Get().FavoriteFolderPaths.AddUnique(ConvertVirtualPathToInvariantPathString(FolderPath)); } void ContentBrowserUtils::RemoveFavoriteFolder(const FString& FolderPath, bool bFlushConfig /*= true*/) { FString InvariantFolder = ConvertVirtualPathToInvariantPathString(FolderPath); // Find and remove any subfolders FContentBrowserSingleton::Get().FavoriteFolderPaths.RemoveAll([&InvariantFolder](const FString& FavoritePath) { return FavoritePath.StartsWith(InvariantFolder) && (FavoritePath.Len() <= InvariantFolder.Len() || FavoritePath[InvariantFolder.Len()] == TEXT('/')); }); if (bFlushConfig) { GConfig->Flush(false, GEditorPerProjectIni); } } const TArray& ContentBrowserUtils::GetFavoriteFolders() { return FContentBrowserSingleton::Get().FavoriteFolderPaths; } void ContentBrowserUtils::AddShowPrivateContentFolder(const FStringView VirtualFolderPath, const FName Owner) { FContentBrowserSingleton& ContentBrowserSingleton = FContentBrowserSingleton::Get(); if (!ContentBrowserSingleton.IsFolderShowPrivateContentToggleable(VirtualFolderPath)) { return; } FName InvariantPath; IContentBrowserDataModule::Get().GetSubsystem()->TryConvertVirtualPath(VirtualFolderPath, InvariantPath); const TSharedPtr& ShowPrivateContentPermissionList = ContentBrowserSingleton.GetShowPrivateContentPermissionList(); ShowPrivateContentPermissionList->AddAllowListItem(Owner, InvariantPath); ContentBrowserSingleton.SetPrivateContentPermissionListDirty(); } void ContentBrowserUtils::RemoveShowPrivateContentFolder(const FStringView VirtualFolderPath, const FName Owner) { FContentBrowserSingleton& ContentBrowserSingleton = FContentBrowserSingleton::Get(); if (!ContentBrowserSingleton.IsFolderShowPrivateContentToggleable(VirtualFolderPath)) { return; } FName InvariantPath; IContentBrowserDataModule::Get().GetSubsystem()->TryConvertVirtualPath(VirtualFolderPath, InvariantPath); const TSharedPtr& ShowPrivateContentPermissionList = ContentBrowserSingleton.GetShowPrivateContentPermissionList(); ShowPrivateContentPermissionList->RemoveAllowListItem(Owner, InvariantPath); ContentBrowserSingleton.SetPrivateContentPermissionListDirty(); } #undef LOCTEXT_NAMESPACE