// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved. #include "ContentBrowserPCH.h" #include "AssetContextMenu.h" #include "NewAssetOrClassContextMenu.h" #include "PathContextMenu.h" #include "ContentBrowserModule.h" #include "ContentBrowserCommands.h" #include "CollectionManagerModule.h" #include "AssetRegistryModule.h" #include "SDockTab.h" #include "GenericCommands.h" #include "IAddContentDialogModule.h" #include "Engine/Selection.h" #include "NativeClassHierarchy.h" #include "GameProjectGenerationModule.h" #define LOCTEXT_NAMESPACE "ContentBrowser" const FString SContentBrowser::SettingsIniSection = TEXT("ContentBrowser"); SContentBrowser::~SContentBrowser() { // Remove the listener for when view settings are changed UContentBrowserSettings::OnSettingChanged().RemoveAll( this ); // Remove listeners for when collections/paths are renamed/deleted FCollectionManagerModule* CollectionManagerModule = FModuleManager::GetModulePtr(TEXT("CollectionManager")); if (CollectionManagerModule != nullptr) { CollectionManagerModule->Get().OnCollectionRenamed().RemoveAll(this); CollectionManagerModule->Get().OnCollectionDestroyed().RemoveAll(this); } FAssetRegistryModule* AssetRegistryModule = FModuleManager::GetModulePtr(TEXT("AssetRegistry")); if (AssetRegistryModule != nullptr) { AssetRegistryModule->Get().OnPathRemoved().RemoveAll(this); } } BEGIN_SLATE_FUNCTION_BUILD_OPTIMIZATION void SContentBrowser::Construct( const FArguments& InArgs, const FName& InInstanceName ) { if ( InArgs._ContainingTab.IsValid() ) { // For content browsers that are placed in tabs, save settings when the tab is closing. ContainingTab = InArgs._ContainingTab; InArgs._ContainingTab->SetOnPersistVisualState( SDockTab::FOnPersistVisualState::CreateSP( this, &SContentBrowser::OnContainingTabSavingVisualState ) ); InArgs._ContainingTab->SetOnTabClosed( SDockTab::FOnTabClosedCallback::CreateSP( this, &SContentBrowser::OnContainingTabClosed ) ); InArgs._ContainingTab->SetOnTabActivated( SDockTab::FOnTabActivatedCallback::CreateSP( this, &SContentBrowser::OnContainingTabActivated ) ); } bIsLocked = InArgs._InitiallyLocked; HistoryManager.SetOnApplyHistoryData(FOnApplyHistoryData::CreateSP(this, &SContentBrowser::OnApplyHistoryData)); HistoryManager.SetOnUpdateHistoryData(FOnUpdateHistoryData::CreateSP(this, &SContentBrowser::OnUpdateHistoryData)); PathContextMenu = MakeShareable(new FPathContextMenu( AsShared() )); PathContextMenu->SetOnNewAssetRequested( FNewAssetOrClassContextMenu::FOnNewAssetRequested::CreateSP(this, &SContentBrowser::NewAssetRequested) ); PathContextMenu->SetOnNewClassRequested( FNewAssetOrClassContextMenu::FOnNewClassRequested::CreateSP(this, &SContentBrowser::NewClassRequested) ); PathContextMenu->SetOnImportAssetRequested(FNewAssetOrClassContextMenu::FOnImportAssetRequested::CreateSP(this, &SContentBrowser::ImportAsset)); PathContextMenu->SetOnRenameFolderRequested(FPathContextMenu::FOnRenameFolderRequested::CreateSP(this, &SContentBrowser::OnRenameFolderRequested)); PathContextMenu->SetOnFolderDeleted(FPathContextMenu::FOnFolderDeleted::CreateSP(this, &SContentBrowser::OnOpenedFolderDeleted)); FrontendFilters = MakeShareable(new FAssetFilterCollectionType()); TextFilter = MakeShareable( new FFrontendFilter_Text() ); static const FName DefaultForegroundName("DefaultForeground"); FContentBrowserCommands::Register(); BindCommands(); ChildSlot [ SNew(SVerticalBox) // Path and history +SVerticalBox::Slot() .AutoHeight() .Padding( 0, 0, 0, 0 ) [ SNew( SWrapBox ) .UseAllottedWidth( true ) .InnerSlotPadding( FVector2D( 5, 2 ) ) + SWrapBox::Slot() .FillLineWhenWidthLessThan( 600 ) .FillEmptySpace( true ) [ SNew( SHorizontalBox ) + SHorizontalBox::Slot() .FillWidth(1.0f) [ SNew( SBorder ) .Padding( FMargin( 3 ) ) .BorderImage( FEditorStyle::GetBrush( "ToolPanel.GroupBorder" ) ) [ SNew( SHorizontalBox ) // New + SHorizontalBox::Slot() .AutoWidth() .VAlign( VAlign_Center ) .HAlign( HAlign_Left ) [ SNew( SComboButton ) .ComboButtonStyle( FEditorStyle::Get(), "ToolbarComboButton" ) .ButtonStyle(FEditorStyle::Get(), "FlatButton.Success") .ForegroundColor(FLinearColor::White) .ContentPadding(FMargin(6, 2)) .OnGetMenuContent_Lambda( [this]{ return MakeAddNewContextMenu( true, false ); } ) .ToolTipText( this, &SContentBrowser::GetAddNewToolTipText ) .IsEnabled( this, &SContentBrowser::IsAddNewEnabled ) .AddMetaData(FTagMetaData(TEXT("ContentBrowserNewAsset"))) .HasDownArrow(false) .ButtonContent() [ SNew( SHorizontalBox ) // New Icon + SHorizontalBox::Slot() .VAlign(VAlign_Center) .AutoWidth() [ SNew(STextBlock) .TextStyle(FEditorStyle::Get(), "ContentBrowser.TopBar.Font") .Font(FEditorStyle::Get().GetFontStyle("FontAwesome.11")) .Text(FText::FromString(FString(TEXT("\xf15b"))) /*fa-file*/) ] // New Text + SHorizontalBox::Slot() .AutoWidth() .VAlign(VAlign_Center) .Padding(4, 0, 0, 0) [ SNew( STextBlock ) .TextStyle( FEditorStyle::Get(), "ContentBrowser.TopBar.Font" ) .Text( LOCTEXT( "NewButton", "Add New" ) ) ] // Down Arrow + SHorizontalBox::Slot() .VAlign(VAlign_Center) .AutoWidth() .Padding(4, 0, 0, 0) [ SNew(STextBlock) .TextStyle(FEditorStyle::Get(), "ContentBrowser.TopBar.Font") .Font(FEditorStyle::Get().GetFontStyle("FontAwesome.10")) .Text(FText::FromString(FString(TEXT("\xf0d7"))) /*fa-caret-down*/) ] ] ] // Import + SHorizontalBox::Slot() .AutoWidth() .VAlign( VAlign_Center ) .HAlign( HAlign_Left ) .Padding(6, 0) [ SNew( SButton ) .ButtonStyle(FEditorStyle::Get(), "FlatButton") .ToolTipText( this, &SContentBrowser::GetImportTooltipText ) .IsEnabled( this, &SContentBrowser::IsImportEnabled ) .OnClicked( this, &SContentBrowser::HandleImportClicked ) .ContentPadding(FMargin(6, 2)) .AddMetaData(FTagMetaData(TEXT("ContentBrowserImportAsset"))) [ SNew( SHorizontalBox ) // Import Icon + SHorizontalBox::Slot() .VAlign(VAlign_Center) .AutoWidth() [ SNew(STextBlock) .TextStyle(FEditorStyle::Get(), "ContentBrowser.TopBar.Font") .Font(FEditorStyle::Get().GetFontStyle("FontAwesome.11")) .Text(FText::FromString(FString(TEXT("\xf019"))) /*fa-download*/) ] // Import Text + SHorizontalBox::Slot() .AutoWidth() .VAlign(VAlign_Center) .Padding(4, 0, 0, 0) [ SNew( STextBlock ) .TextStyle( FEditorStyle::Get(), "ContentBrowser.TopBar.Font" ) .Text( LOCTEXT( "Import", "Import" ) ) ] ] ] // Save + SHorizontalBox::Slot() .FillWidth(1.0f) .VAlign(VAlign_Center) .HAlign(HAlign_Left) [ SNew( SButton ) .ButtonStyle(FEditorStyle::Get(), "FlatButton") .ToolTipText( LOCTEXT( "SaveDirtyPackagesTooltip", "Save all modified assets." ) ) .ContentPadding(FMargin(6, 2)) .OnClicked( this, &SContentBrowser::OnSaveClicked ) .AddMetaData(FTagMetaData(TEXT("ContentBrowserSaveDirtyPackages"))) [ SNew( SHorizontalBox ) // Save All Icon + SHorizontalBox::Slot() .VAlign(VAlign_Center) .AutoWidth() [ SNew(STextBlock) .TextStyle(FEditorStyle::Get(), "ContentBrowser.TopBar.Font") .Font(FEditorStyle::Get().GetFontStyle("FontAwesome.11")) .Text(FText::FromString(FString(TEXT("\xf0c7"))) /*fa-floppy-o*/) ] // Save All Text + SHorizontalBox::Slot() .AutoWidth() .VAlign(VAlign_Center) .Padding(4, 0, 0, 0) [ SNew( STextBlock ) .TextStyle( FEditorStyle::Get(), "ContentBrowser.TopBar.Font" ) .Text( LOCTEXT( "SaveAll", "Save All" ) ) ] ] ] ] ] ] + SWrapBox::Slot() .FillEmptySpace( true ) [ SNew(SBorder) .Padding(FMargin(3)) .BorderImage( FEditorStyle::GetBrush("ToolPanel.GroupBorder") ) [ SNew(SHorizontalBox) // History Back Button +SHorizontalBox::Slot() .AutoWidth() [ SNew(SVerticalBox) + SVerticalBox::Slot() .FillHeight(1.0f) [ SNew(SButton) .VAlign(EVerticalAlignment::VAlign_Center) .ButtonStyle(FEditorStyle::Get(), "FlatButton") .ForegroundColor( FEditorStyle::GetSlateColor(DefaultForegroundName) ) .ToolTipText( this, &SContentBrowser::GetHistoryBackTooltip ) .ContentPadding( FMargin(1, 0) ) .OnClicked(this, &SContentBrowser::BackClicked) .IsEnabled(this, &SContentBrowser::IsBackEnabled) .AddMetaData(FTagMetaData(TEXT("ContentBrowserHistoryBack"))) [ SNew(STextBlock) .TextStyle(FEditorStyle::Get(), "ContentBrowser.TopBar.Font") .Font(FEditorStyle::Get().GetFontStyle("FontAwesome.11")) .Text(FText::FromString(FString(TEXT("\xf060"))) /*fa-arrow-left*/) ] ] ] // History Forward Button + SHorizontalBox::Slot() .AutoWidth() [ SNew(SVerticalBox) + SVerticalBox::Slot() .FillHeight(1.0f) [ SNew(SButton) .VAlign(EVerticalAlignment::VAlign_Center) .ButtonStyle(FEditorStyle::Get(), "FlatButton") .ForegroundColor( FEditorStyle::GetSlateColor(DefaultForegroundName) ) .ToolTipText( this, &SContentBrowser::GetHistoryForwardTooltip ) .ContentPadding( FMargin(1, 0) ) .OnClicked(this, &SContentBrowser::ForwardClicked) .IsEnabled(this, &SContentBrowser::IsForwardEnabled) .AddMetaData(FTagMetaData(TEXT("ContentBrowserHistoryForward"))) [ SNew(STextBlock) .TextStyle(FEditorStyle::Get(), "ContentBrowser.TopBar.Font") .Font(FEditorStyle::Get().GetFontStyle("FontAwesome.11")) .Text(FText::FromString(FString(TEXT("\xf061"))) /*fa-arrow-right*/) ] ] ] // Separator + SHorizontalBox::Slot() .AutoWidth() .Padding(3, 0) [ SNew(SSeparator) .Orientation(Orient_Vertical) ] // Path picker + SHorizontalBox::Slot() .AutoWidth() .VAlign( VAlign_Fill ) [ SAssignNew( PathPickerButton, SComboButton ) .ButtonStyle(FEditorStyle::Get(), "FlatButton") .ForegroundColor(FLinearColor::White) .ToolTipText( LOCTEXT( "PathPickerTooltip", "Choose a path" ) ) .OnGetMenuContent( this, &SContentBrowser::GetPathPickerContent ) .HasDownArrow( false ) .AddMetaData(FTagMetaData(TEXT("ContentBrowserPathPicker"))) .ContentPadding(FMargin(3, 3)) .ButtonContent() [ SNew(STextBlock) .TextStyle(FEditorStyle::Get(), "ContentBrowser.TopBar.Font") .Font(FEditorStyle::Get().GetFontStyle("FontAwesome.11")) .Text(FText::FromString(FString(TEXT("\xf07c"))) /*fa-folder-open*/) ] ] // Path + SHorizontalBox::Slot() .VAlign(VAlign_Center) .HAlign(HAlign_Left) .FillWidth(1.0f) .Padding(FMargin(0)) [ SAssignNew(PathBreadcrumbTrail, SBreadcrumbTrail) .ButtonContentPadding(FMargin(2, 2)) .ButtonStyle(FEditorStyle::Get(), "FlatButton") .DelimiterImage(FEditorStyle::GetBrush("ContentBrowser.PathDelimiter")) .TextStyle(FEditorStyle::Get(), "ContentBrowser.PathText") .ShowLeadingDelimiter(false) .InvertTextColorOnHover(false) .OnCrumbClicked(this, &SContentBrowser::OnPathClicked) .GetCrumbMenuContent(this, &SContentBrowser::OnGetCrumbDelimiterContent) .AddMetaData(FTagMetaData(TEXT("ContentBrowserPath"))) ] // Lock button + SHorizontalBox::Slot() .AutoWidth() .VAlign(VAlign_Center) [ SNew(SVerticalBox) + SVerticalBox::Slot() .FillHeight(1.0f) [ SNew(SButton) .VAlign(EVerticalAlignment::VAlign_Center) .ButtonStyle(FEditorStyle::Get(), "FlatButton") .ToolTipText( LOCTEXT("LockToggleTooltip", "Toggle lock. If locked, this browser will ignore Find in Content Browser requests.") ) .ContentPadding( FMargin(1, 0) ) .OnClicked(this, &SContentBrowser::ToggleLockClicked) .AddMetaData(FTagMetaData(TEXT("ContentBrowserLock"))) [ SNew(SImage) .Image( this, &SContentBrowser::GetToggleLockImage) ] ] ] ] ] ] // Assets/tree + SVerticalBox::Slot() .FillHeight(1.0f) [ // The tree/assets splitter SAssignNew(PathAssetSplitterPtr, SSplitter) // Sources View + SSplitter::Slot() .Value(0.15f) [ SNew(SVerticalBox) .Visibility( this, &SContentBrowser::GetSourcesViewVisibility ) + SVerticalBox::Slot() .FillHeight(1.0f) [ SAssignNew(PathCollectionSplitterPtr, SSplitter) .Style( FEditorStyle::Get(), "ContentBrowser.Splitter" ) .Orientation( Orient_Vertical ) // Path View + SSplitter::Slot() .Value(0.9f) [ SNew(SBorder) .Padding(FMargin(3)) .BorderImage( FEditorStyle::GetBrush("ToolPanel.GroupBorder") ) [ SAssignNew( PathViewPtr, SPathView ) .OnPathSelected( this, &SContentBrowser::PathSelected ) .OnGetFolderContextMenu( this, &SContentBrowser::GetFolderContextMenu, true ) .OnGetPathContextMenuExtender( this, &SContentBrowser::GetPathContextMenuExtender ) .FocusSearchBoxWhenOpened( false ) .ShowTreeTitle( false ) .ShowSeparator( false ) .AllowClassesFolder( true ) .AddMetaData(FTagMetaData(TEXT("ContentBrowserSources"))) .SearchContent() [ SNew( SVerticalBox ) .AddMetaData(FTagMetaData(TEXT("ContentBrowserSourcesToggle1"))) + SVerticalBox::Slot() .FillHeight( 1.0f ) .Padding(0,0,2,0) [ SNew( SButton ) .VAlign( EVerticalAlignment::VAlign_Center ) .ButtonStyle( FEditorStyle::Get(), "ToggleButton" ) .ToolTipText( LOCTEXT( "SourcesTreeToggleTooltip", "Show or hide the sources panel" ) ) .ContentPadding( FMargin( 1, 0 ) ) .ForegroundColor( FEditorStyle::GetSlateColor(DefaultForegroundName) ) .OnClicked( this, &SContentBrowser::SourcesViewExpandClicked ) [ SNew( SImage ) .Image( this, &SContentBrowser::GetSourcesToggleImage ) ] ] ] ] ] // Collection View + SSplitter::Slot() .Value(0.9f) [ SNew(SBorder) .Visibility( this, &SContentBrowser::GetCollectionViewVisibility ) .Padding(FMargin(3)) .BorderImage( FEditorStyle::GetBrush("ToolPanel.GroupBorder") ) [ SAssignNew(CollectionViewPtr, SCollectionView) .OnCollectionSelected(this, &SContentBrowser::CollectionSelected) .AddMetaData(FTagMetaData(TEXT("ContentBrowserCollections"))) ] ] ] ] // Asset View + SSplitter::Slot() .Value(0.7f) [ SNew(SBorder) .Padding(FMargin(3)) .BorderImage( FEditorStyle::GetBrush("ToolPanel.GroupBorder") ) [ SNew(SVerticalBox) // Search and commands + SVerticalBox::Slot() .AutoHeight() [ SNew(SHorizontalBox) // Expand/collapse sources button + SHorizontalBox::Slot() .AutoWidth() .Padding( 0, 0, 4, 0 ) [ SNew( SVerticalBox ) .AddMetaData(FTagMetaData(TEXT("ContentBrowserSourcesToggle2"))) + SVerticalBox::Slot() .FillHeight( 1.0f ) [ SNew( SButton ) .VAlign( EVerticalAlignment::VAlign_Center ) .ButtonStyle( FEditorStyle::Get(), "ToggleButton" ) .ToolTipText( LOCTEXT( "SourcesTreeToggleTooltip", "Show or hide the sources panel" ) ) .ContentPadding( FMargin( 1, 0 ) ) .ForegroundColor( FEditorStyle::GetSlateColor(DefaultForegroundName) ) .OnClicked( this, &SContentBrowser::SourcesViewExpandClicked ) .Visibility( this, &SContentBrowser::GetPathExpanderVisibility ) [ SNew( SImage ) .Image( this, &SContentBrowser::GetSourcesToggleImage ) ] ] ] // Filter + SHorizontalBox::Slot() .AutoWidth() [ SNew( SComboButton ) .ComboButtonStyle( FEditorStyle::Get(), "ContentBrowser.Filters.Style" ) .ForegroundColor(FLinearColor::White) .ContentPadding(0) .ToolTipText( LOCTEXT( "AddFilterToolTip", "Add an asset filter." ) ) .OnGetMenuContent( this, &SContentBrowser::MakeAddFilterMenu ) .HasDownArrow( true ) .ContentPadding( FMargin( 1, 0 ) ) .AddMetaData(FTagMetaData(TEXT("ContentBrowserFiltersCombo"))) .ButtonContent() [ SNew(SHorizontalBox) + SHorizontalBox::Slot() .AutoWidth() [ SNew(STextBlock) .TextStyle(FEditorStyle::Get(), "ContentBrowser.Filters.Text") .Font(FEditorStyle::Get().GetFontStyle("FontAwesome.9")) .Text(FText::FromString(FString(TEXT("\xf0b0"))) /*fa-filter*/) ] + SHorizontalBox::Slot() .AutoWidth() .Padding(2,0,0,0) [ SNew(STextBlock) .TextStyle(FEditorStyle::Get(), "ContentBrowser.Filters.Text") .Text(LOCTEXT("Filters", "Filters")) ] ] ] // Search +SHorizontalBox::Slot() .Padding(4, 0, 0, 0) .VAlign(VAlign_Center) .FillWidth(1.0f) [ SAssignNew(SearchBoxPtr, SAssetSearchBox) .HintText( this, &SContentBrowser::GetSearchAssetsHintText ) .OnTextChanged( this, &SContentBrowser::OnSearchBoxChanged ) .OnTextCommitted( this, &SContentBrowser::OnSearchBoxCommitted ) .PossibleSuggestions( this, &SContentBrowser::GetAssetSearchSuggestions ) .DelayChangeNotificationsWhileTyping( true ) .AddMetaData(FTagMetaData(TEXT("ContentBrowserSearchAssets"))) ] ] // Filters + SVerticalBox::Slot() .AutoHeight() [ SAssignNew(FilterListPtr, SFilterList) .OnFilterChanged(this, &SContentBrowser::OnFilterChanged) .OnGetContextMenu(this, &SContentBrowser::GetFilterContextMenu) .FrontendFilters(FrontendFilters) .AddMetaData(FTagMetaData(TEXT("ContentBrowserFilters"))) ] // Assets + SVerticalBox::Slot() .FillHeight( 1.0f ) .Padding( 0 ) [ SAssignNew(AssetViewPtr, SAssetView) .ThumbnailScale( 0.18 ) .OnPathSelected(this, &SContentBrowser::FolderEntered) .OnAssetSelected(this, &SContentBrowser::OnAssetSelectionChanged) .OnAssetsActivated(this, &SContentBrowser::OnAssetsActivated) .OnGetAssetContextMenu(this, &SContentBrowser::OnGetAssetContextMenu) .OnGetFolderContextMenu(this, &SContentBrowser::GetFolderContextMenu, false) .OnGetPathContextMenuExtender(this, &SContentBrowser::GetPathContextMenuExtender) .OnFindInAssetTreeRequested(this, &SContentBrowser::OnFindInAssetTreeRequested) .OnAssetRenameCommitted(this, &SContentBrowser::OnAssetRenameCommitted) .AreRealTimeThumbnailsAllowed(this, &SContentBrowser::IsHovered) .FrontendFilters(FrontendFilters) .HighlightedText(this, &SContentBrowser::GetHighlightedText) .AllowThumbnailEditMode(true) .AllowThumbnailHintLabel(false) .CanShowFolders(true) .CanShowRealTimeThumbnails(true) .CanShowDevelopersFolder(true) .AddMetaData(FTagMetaData(TEXT("ContentBrowserAssets"))) ] ] ] ] ]; AssetContextMenu = MakeShareable(new FAssetContextMenu(AssetViewPtr)); AssetContextMenu->BindCommands(Commands); AssetContextMenu->SetOnFindInAssetTreeRequested( FOnFindInAssetTreeRequested::CreateSP(this, &SContentBrowser::OnFindInAssetTreeRequested) ); AssetContextMenu->SetOnRenameRequested( FAssetContextMenu::FOnRenameRequested::CreateSP(this, &SContentBrowser::OnRenameRequested) ); AssetContextMenu->SetOnRenameFolderRequested( FAssetContextMenu::FOnRenameFolderRequested::CreateSP(this, &SContentBrowser::OnRenameFolderRequested) ); AssetContextMenu->SetOnDuplicateRequested( FAssetContextMenu::FOnDuplicateRequested::CreateSP(this, &SContentBrowser::OnDuplicateRequested) ); AssetContextMenu->SetOnAssetViewRefreshRequested( FAssetContextMenu::FOnAssetViewRefreshRequested::CreateSP( this, &SContentBrowser::OnAssetViewRefreshRequested) ); // Select /Game by default FSourcesData DefaultSourcesData; TArray SelectedPaths; DefaultSourcesData.PackagePaths.Add(TEXT("/Game")); SelectedPaths.Add(TEXT("/Game")); PathViewPtr->SetSelectedPaths(SelectedPaths); AssetViewPtr->SetSourcesData(DefaultSourcesData); // Set the initial history data HistoryManager.AddHistoryData(); // Load settings if they were specified this->InstanceName = InInstanceName; LoadSettings(InInstanceName); // Bindings to manage history when items are deleted FCollectionManagerModule& CollectionManagerModule = FModuleManager::LoadModuleChecked(TEXT("CollectionManager")); CollectionManagerModule.Get().OnCollectionRenamed().AddSP(this, &SContentBrowser::HandleCollectionRenamed); CollectionManagerModule.Get().OnCollectionDestroyed().AddSP(this, &SContentBrowser::HandleCollectionRemoved); FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked(TEXT("AssetRegistry")); AssetRegistryModule.Get().OnPathRemoved().AddSP(this, &SContentBrowser::HandlePathRemoved); // We want to be able to search the feature packs in the super search so we need the module loaded IAddContentDialogModule& AddContentDialogModule = FModuleManager::LoadModuleChecked("AddContentDialog"); const TWeakPtr WeakThis = SharedThis(this); FContentBrowserModule& ContentBrowserModule = FModuleManager::GetModuleChecked( TEXT("ContentBrowser") ); ContentBrowserModule.GetAllAssetViewViewMenuExtenders().Add( FContentBrowserMenuExtender::CreateLambda( [=]() { TSharedRef Extender = MakeShareable(new FExtender); if( WeakThis.IsValid() ) { Extender->AddMenuExtension(FName("Folders"), EExtensionHook::After, nullptr, FMenuExtensionDelegate::CreateSP(WeakThis.Pin().ToSharedRef(), &SContentBrowser::ExtendAssetViewMenu)); } return Extender; }) ); // Update the breadcrumb trail path UpdatePath(); } END_SLATE_FUNCTION_BUILD_OPTIMIZATION void SContentBrowser::BindCommands() { Commands = TSharedPtr< FUICommandList >(new FUICommandList); Commands->MapAction(FGenericCommands::Get().Rename, FUIAction( FExecuteAction::CreateSP(this, &SContentBrowser::OnRename), FCanExecuteAction::CreateSP(this, &SContentBrowser::CanRename) )); Commands->MapAction(FGenericCommands::Get().Delete, FUIAction( FExecuteAction::CreateSP(this, &SContentBrowser::OnDelete), FCanExecuteAction::CreateSP(this, &SContentBrowser::CanDelete) )); Commands->MapAction(FContentBrowserCommands::Get().OpenAssetsOrFolders, FUIAction( FExecuteAction::CreateSP(this, &SContentBrowser::OnOpenAssetsOrFolders) )); Commands->MapAction(FContentBrowserCommands::Get().PreviewAssets, FUIAction( FExecuteAction::CreateSP(this, &SContentBrowser::OnPreviewAssets) )); Commands->MapAction( FContentBrowserCommands::Get().DirectoryUp, FUIAction( FExecuteAction::CreateSP( this, &SContentBrowser::OnDirectoryUp ) )); } void SContentBrowser::ExtendAssetViewMenu( FMenuBuilder& MenuBuilder ) { MenuBuilder.AddMenuEntry( LOCTEXT("ShowCollectionOption", "Show Collections"), LOCTEXT("ShowCollectionOptionToolTip", "Show the collections list in the view."), FSlateIcon(), FUIAction( FExecuteAction::CreateSP(this, &SContentBrowser::ToggleShowCollections), FCanExecuteAction(), FIsActionChecked::CreateSP(this, &SContentBrowser::IsShowingCollections) ), NAME_None, EUserInterfaceActionType::ToggleButton ); } void SContentBrowser::ToggleShowCollections() { GetMutableDefault()->SetDisplayCollections(!GetDefault()->GetDisplayCollections()); GetMutableDefault()->PostEditChange(); } bool SContentBrowser::IsShowingCollections() const { return GetDefault()->GetDisplayCollections(); } EVisibility SContentBrowser::GetCollectionViewVisibility() const { return IsShowingCollections() ? EVisibility::Visible : EVisibility::Collapsed; } FText SContentBrowser::GetHighlightedText() const { return TextFilter->GetRawFilterText(); } void SContentBrowser::CreateNewAsset(const FString& DefaultAssetName, const FString& PackagePath, UClass* AssetClass, UFactory* Factory) { AssetViewPtr->CreateNewAsset(DefaultAssetName, PackagePath, AssetClass, Factory); } bool SContentBrowser::IsImportEnabled() const { const FSourcesData& SourcesData = AssetViewPtr->GetSourcesData(); return SourcesData.PackagePaths.Num() == 1 && !ContentBrowserUtils::IsClassPath(SourcesData.PackagePaths[0].ToString()); } FText SContentBrowser::GetImportTooltipText() const { const FSourcesData& SourcesData = AssetViewPtr->GetSourcesData(); if ( SourcesData.PackagePaths.Num() == 1 ) { const FString CurrentPath = SourcesData.PackagePaths[0].ToString(); if ( ContentBrowserUtils::IsClassPath( CurrentPath ) ) { return LOCTEXT( "ImportAssetToolTip_InvalidClassPath", "Cannot import assets to class paths." ); } else { return FText::Format( LOCTEXT( "ImportAssetToolTip", "Import to {0}..." ), FText::FromString( CurrentPath ) ); } } else if ( SourcesData.PackagePaths.Num() > 1 ) { return LOCTEXT( "ImportAssetToolTip_MultiplePaths", "Cannot import assets to multiple paths." ); } return LOCTEXT( "ImportAssetToolTip_NoPath", "No path is selected as an import target." ); } FReply SContentBrowser::HandleImportClicked() { ImportAsset( GetCurrentPath() ); return FReply::Handled(); } void SContentBrowser::ImportAsset( const FString& InPath ) { if ( ensure( !InPath.IsEmpty() ) ) { FAssetToolsModule& AssetToolsModule = FModuleManager::Get().LoadModuleChecked( "AssetTools" ); AssetToolsModule.Get().ImportAssets( InPath ); } } void SContentBrowser::SyncToAssets( const TArray& AssetDataList, const bool bAllowImplicitSync, const bool bDisableFiltersThatHideAssets ) { // Check to see if any of the assets require certain folders to be visible const UContentBrowserSettings* tmp = GetDefault(); bool bDisplayDev = GetDefault()->GetDisplayDevelopersFolder(); bool bDisplayEngine = GetDefault()->GetDisplayEngineFolder(); bool bDisplayPlugins = GetDefault()->GetDisplayPluginFolders(); if ( !bDisplayDev || !bDisplayEngine || !bDisplayPlugins ) { bool bRepopulate = false; for (int32 AssetIdx = AssetDataList.Num() - 1; AssetIdx >= 0 && ( !bDisplayDev || !bDisplayEngine || !bDisplayPlugins ); --AssetIdx) { const FAssetData& Item = AssetDataList[AssetIdx]; FString PackagePath; if ( Item.AssetClass == NAME_Class ) { // Classes are found in the /Classes_ roots TSharedRef NativeClassHierarchy = FContentBrowserSingleton::Get().GetNativeClassHierarchy(); NativeClassHierarchy->GetClassPath(Cast(Item.GetAsset()), PackagePath, false/*bIncludeClassName*/); } else { // All other assets are found by their package path PackagePath = Item.PackagePath.ToString(); } const ContentBrowserUtils::ECBFolderCategory FolderCategory = ContentBrowserUtils::GetFolderCategory( PackagePath ); if ( !bDisplayDev && FolderCategory == ContentBrowserUtils::ECBFolderCategory::DeveloperContent ) { bDisplayDev = true; GetMutableDefault()->SetDisplayDevelopersFolder(true, true); bRepopulate = true; } else if ( !bDisplayEngine && (FolderCategory == ContentBrowserUtils::ECBFolderCategory::EngineContent || FolderCategory == ContentBrowserUtils::ECBFolderCategory::EngineClasses) ) { bDisplayEngine = true; GetMutableDefault()->SetDisplayEngineFolder(true, true); bRepopulate = true; } else if ( !bDisplayPlugins && (FolderCategory == ContentBrowserUtils::ECBFolderCategory::PluginContent || FolderCategory == ContentBrowserUtils::ECBFolderCategory::PluginClasses) ) { bDisplayPlugins = true; GetMutableDefault()->SetDisplayPluginFolders(true, true); bRepopulate = true; } } // If we have auto-enabled any flags, force a refresh if ( bRepopulate ) { PathViewPtr->Populate(); } } if ( bDisableFiltersThatHideAssets ) { // Disable the filter categories FilterListPtr->DisableFiltersThatHideAssets(AssetDataList); } // Disable the filter search (reset the filter, then clear the search text) // Note: we have to remove the filter immediately, we can't wait for OnSearchBoxChanged to hit SetSearchBoxText(FText::GetEmpty()); SearchBoxPtr->SetText(FText::GetEmpty()); // Tell the sources view first so the asset view will be up to date by the time we request the sync PathViewPtr->SyncToAssets(AssetDataList, bAllowImplicitSync); AssetViewPtr->SyncToAssets(AssetDataList); } void SContentBrowser::SetIsPrimaryContentBrowser (bool NewIsPrimary) { bIsPrimaryBrowser = NewIsPrimary; if ( bIsPrimaryBrowser ) { SyncGlobalSelectionSet(); } else { USelection* EditorSelection = GEditor->GetSelectedObjects(); if ( ensure( EditorSelection != NULL ) ) { EditorSelection->DeselectAll(); } } } TSharedPtr SContentBrowser::GetTabManager() const { if ( ContainingTab.IsValid() ) { return ContainingTab.Pin()->GetTabManager(); } return NULL; } void SContentBrowser::LoadSelectedObjectsIfNeeded() { // Get the selected assets in the asset view const TArray& SelectedAssets = AssetViewPtr->GetSelectedAssets(); // Load every asset that isn't already in memory for ( auto AssetIt = SelectedAssets.CreateConstIterator(); AssetIt; ++AssetIt ) { const FAssetData& AssetData = *AssetIt; const bool bShowProgressDialog = (!AssetData.IsAssetLoaded() && FEditorFileUtils::IsMapPackageAsset(AssetData.ObjectPath.ToString())); GWarn->BeginSlowTask(LOCTEXT("LoadingObjects", "Loading Objects..."), bShowProgressDialog); (*AssetIt).GetAsset(); GWarn->EndSlowTask(); } // Sync the global selection set if we are the primary browser if ( bIsPrimaryBrowser ) { SyncGlobalSelectionSet(); } } void SContentBrowser::GetSelectedAssets(TArray& SelectedAssets) { // Make sure the asset data is up to date AssetViewPtr->ProcessRecentlyLoadedOrChangedAssets(); SelectedAssets = AssetViewPtr->GetSelectedAssets(); } void SContentBrowser::SaveSettings() const { const FString& SettingsString = InstanceName.ToString(); GConfig->SetBool(*SettingsIniSection, *(SettingsString + TEXT(".SourcesExpanded")), bSourcesViewExpanded, GEditorPerProjectIni); GConfig->SetBool(*SettingsIniSection, *(SettingsString + TEXT(".Locked")), bIsLocked, GEditorPerProjectIni); for(int32 SlotIndex = 0; SlotIndex < PathAssetSplitterPtr->GetChildren()->Num(); SlotIndex++) { float SplitterSize = PathAssetSplitterPtr->SlotAt(SlotIndex).SizeValue.Get(); GConfig->SetFloat(*SettingsIniSection, *(SettingsString + FString::Printf(TEXT(".VerticalSplitter.SlotSize%d"), SlotIndex)), SplitterSize, GEditorPerProjectIni); } for(int32 SlotIndex = 0; SlotIndex < PathCollectionSplitterPtr->GetChildren()->Num(); SlotIndex++) { float SplitterSize = PathCollectionSplitterPtr->SlotAt(SlotIndex).SizeValue.Get(); GConfig->SetFloat(*SettingsIniSection, *(SettingsString + FString::Printf(TEXT(".HorizontalSplitter.SlotSize%d"), SlotIndex)), SplitterSize, GEditorPerProjectIni); } // Save all our data using the settings string as a key in the user settings ini FilterListPtr->SaveSettings(GEditorPerProjectIni, SettingsIniSection, SettingsString); PathViewPtr->SaveSettings(GEditorPerProjectIni, SettingsIniSection, SettingsString); CollectionViewPtr->SaveSettings(GEditorPerProjectIni, SettingsIniSection, SettingsString); AssetViewPtr->SaveSettings(GEditorPerProjectIni, SettingsIniSection, SettingsString); } const FName SContentBrowser::GetInstanceName() const { return InstanceName; } bool SContentBrowser::IsLocked() const { return bIsLocked; } void SContentBrowser::SetKeyboardFocusOnSearch() const { // Focus on the search box FSlateApplication::Get().SetKeyboardFocus( SearchBoxPtr, EFocusCause::SetDirectly ); } FReply SContentBrowser::OnKeyDown( const FGeometry& MyGeometry, const FKeyEvent& InKeyEvent ) { if( Commands->ProcessCommandBindings( InKeyEvent ) ) { return FReply::Handled(); } return FReply::Unhandled(); } FReply SContentBrowser::OnPreviewMouseButtonDown( const FGeometry& MyGeometry, const FPointerEvent& MouseEvent ) { // Clicking in a content browser will shift it to be the primary browser FContentBrowserSingleton::Get().SetPrimaryContentBrowser(SharedThis(this)); return FReply::Unhandled(); } FReply SContentBrowser::OnMouseButtonDown( const FGeometry& MyGeometry, const FPointerEvent& MouseEvent ) { // Mouse back and forward buttons traverse history if ( MouseEvent.GetEffectingButton() == EKeys::ThumbMouseButton) { HistoryManager.GoBack(); return FReply::Handled(); } else if ( MouseEvent.GetEffectingButton() == EKeys::ThumbMouseButton2) { HistoryManager.GoForward(); return FReply::Handled(); } return FReply::Unhandled(); } FReply SContentBrowser::OnMouseButtonDoubleClick( const FGeometry& InMyGeometry, const FPointerEvent& InMouseEvent ) { // Mouse back and forward buttons traverse history if ( InMouseEvent.GetEffectingButton() == EKeys::ThumbMouseButton) { HistoryManager.GoBack(); return FReply::Handled(); } else if ( InMouseEvent.GetEffectingButton() == EKeys::ThumbMouseButton2) { HistoryManager.GoForward(); return FReply::Handled(); } return FReply::Unhandled(); } void SContentBrowser::OnContainingTabSavingVisualState() const { SaveSettings(); } void SContentBrowser::OnContainingTabClosed(TSharedRef DockTab) { FContentBrowserSingleton::Get().ContentBrowserClosed( SharedThis(this) ); } void SContentBrowser::OnContainingTabActivated(TSharedRef DockTab, ETabActivationCause InActivationCause) { if(InActivationCause == ETabActivationCause::UserClickedOnTab) { FContentBrowserSingleton::Get().SetPrimaryContentBrowser(SharedThis(this)); } } void SContentBrowser::LoadSettings(const FName& InInstanceName) { FString SettingsString = InInstanceName.ToString(); // Test to see if we should load legacy settings from a previous instance name // First make sure there aren't any existing settings with the given instance name bool TestBool; if ( !GConfig->GetBool(*SettingsIniSection, *(SettingsString + TEXT(".SourcesExpanded")), TestBool, GEditorPerProjectIni) ) { // If there were not any settings and we are Content Browser 1, see if we have any settings under the legacy name "LevelEditorContentBrowser" if ( InInstanceName.ToString() == TEXT("ContentBrowserTab1") && GConfig->GetBool(*SettingsIniSection, TEXT("LevelEditorContentBrowser.SourcesExpanded"), TestBool, GEditorPerProjectIni) ) { // We have found some legacy settings with the old ID, use them. These settings will be saved out to the new id later SettingsString = TEXT("LevelEditorContentBrowser"); } // else see if we are Content Browser 2, and see if we have any settings under the legacy name "MajorContentBrowserTab" else if ( InInstanceName.ToString() == TEXT("ContentBrowserTab2") && GConfig->GetBool(*SettingsIniSection, TEXT("MajorContentBrowserTab.SourcesExpanded"), TestBool, GEditorPerProjectIni) ) { // We have found some legacy settings with the old ID, use them. These settings will be saved out to the new id later SettingsString = TEXT("MajorContentBrowserTab"); } } // Now that we have determined the appropriate settings string, actually load the settings GConfig->GetBool(*SettingsIniSection, *(SettingsString + TEXT(".SourcesExpanded")), bSourcesViewExpanded, GEditorPerProjectIni); GConfig->GetBool(*SettingsIniSection, *(SettingsString + TEXT(".Locked")), bIsLocked, GEditorPerProjectIni); for(int32 SlotIndex = 0; SlotIndex < PathAssetSplitterPtr->GetChildren()->Num(); SlotIndex++) { float SplitterSize = PathAssetSplitterPtr->SlotAt(SlotIndex).SizeValue.Get(); GConfig->GetFloat(*SettingsIniSection, *(SettingsString + FString::Printf(TEXT(".VerticalSplitter.SlotSize%d"), SlotIndex)), SplitterSize, GEditorPerProjectIni); PathAssetSplitterPtr->SlotAt(SlotIndex).SizeValue = SplitterSize; } for(int32 SlotIndex = 0; SlotIndex < PathCollectionSplitterPtr->GetChildren()->Num(); SlotIndex++) { float SplitterSize = PathCollectionSplitterPtr->SlotAt(SlotIndex).SizeValue.Get(); GConfig->GetFloat(*SettingsIniSection, *(SettingsString + FString::Printf(TEXT(".HorizontalSplitter.SlotSize%d"), SlotIndex)), SplitterSize, GEditorPerProjectIni); PathCollectionSplitterPtr->SlotAt(SlotIndex).SizeValue = SplitterSize; } // Save all our data using the settings string as a key in the user settings ini FilterListPtr->LoadSettings(GEditorPerProjectIni, SettingsIniSection, SettingsString); PathViewPtr->LoadSettings(GEditorPerProjectIni, SettingsIniSection, SettingsString); CollectionViewPtr->LoadSettings(GEditorPerProjectIni, SettingsIniSection, SettingsString); AssetViewPtr->LoadSettings(GEditorPerProjectIni, SettingsIniSection, SettingsString); } void SContentBrowser::SourcesChanged(const TArray& SelectedPaths, const TArray& SelectedCollections) { FString NewSource = SelectedPaths.Num() > 0 ? SelectedPaths[0] : (SelectedCollections.Num() > 0 ? SelectedCollections[0].Name.ToString() : TEXT("None")); UE_LOG(LogContentBrowser, VeryVerbose, TEXT("The content browser source was changed by the sources view to '%s'"), *NewSource); FSourcesData SourcesData; for (int32 PathIdx = 0; PathIdx < SelectedPaths.Num(); ++PathIdx) { SourcesData.PackagePaths.Add(FName(*SelectedPaths[PathIdx])); } SourcesData.Collections = SelectedCollections; if (!AssetViewPtr->GetSourcesData().IsEmpty()) { // Update the current history data to preserve selection if there is a valid SourcesData HistoryManager.UpdateHistoryData(); } // Change the filter for the asset view AssetViewPtr->SetSourcesData(SourcesData); // Add a new history data now that the source has changed HistoryManager.AddHistoryData(); // Update the breadcrumb trail path UpdatePath(); } void SContentBrowser::FolderEntered(const FString& FolderPath) { // set the path view to the incoming path TArray SelectedPaths; SelectedPaths.Add(FolderPath); PathViewPtr->SetSelectedPaths(SelectedPaths); PathSelected(FolderPath); } void SContentBrowser::PathSelected(const FString& FolderPath) { // You may not select both collections and paths CollectionViewPtr->ClearSelection(); TArray SelectedPaths = PathViewPtr->GetSelectedPaths(); TArray SelectedCollections; SourcesChanged(SelectedPaths, SelectedCollections); // Notify 'asset path changed' delegate FContentBrowserModule& ContentBrowserModule = FModuleManager::GetModuleChecked( TEXT("ContentBrowser") ); FContentBrowserModule::FOnAssetPathChanged& PathChangedDelegate = ContentBrowserModule.GetOnAssetPathChanged(); if(PathChangedDelegate.IsBound()) { PathChangedDelegate.Broadcast(FolderPath); } // Update the context menu's selected paths list PathContextMenu->SetSelectedPaths(SelectedPaths); } TSharedRef SContentBrowser::GetPathContextMenuExtender(const TArray& InSelectedPaths) const { return PathContextMenu->MakePathViewContextMenuExtender(InSelectedPaths); } void SContentBrowser::CollectionSelected(const FCollectionNameType& SelectedCollection) { // You may not select both collections and paths PathViewPtr->ClearSelection(); TArray SelectedCollections = CollectionViewPtr->GetSelectedCollections(); TArray SelectedPaths; if( SelectedCollections.Num() == 0 ) { // just select the game folder SelectedPaths.Add(TEXT("/Game")); SourcesChanged(SelectedPaths, SelectedCollections); } else { SourcesChanged(SelectedPaths, SelectedCollections); } } void SContentBrowser::PathPickerPathSelected(const FString& FolderPath) { PathPickerButton->SetIsOpen(false); if ( !FolderPath.IsEmpty() ) { TArray Paths; Paths.Add(FolderPath); PathViewPtr->SetSelectedPaths(Paths); } PathSelected(FolderPath); } void SContentBrowser::PathPickerCollectionSelected(const FCollectionNameType& SelectedCollection) { PathPickerButton->SetIsOpen(false); TArray Collections; Collections.Add(SelectedCollection); CollectionViewPtr->SetSelectedCollections(Collections); CollectionSelected(SelectedCollection); } void SContentBrowser::OnApplyHistoryData ( const FHistoryData& History ) { PathViewPtr->ApplyHistoryData(History); CollectionViewPtr->ApplyHistoryData(History); AssetViewPtr->ApplyHistoryData(History); // Update the breadcrumb trail path UpdatePath(); } void SContentBrowser::OnUpdateHistoryData(FHistoryData& HistoryData) const { const FSourcesData& SourcesData = AssetViewPtr->GetSourcesData(); const TArray& SelectedAssets = AssetViewPtr->GetSelectedAssets(); const FText NewSource = SourcesData.PackagePaths.Num() > 0 ? FText::FromName(SourcesData.PackagePaths[0]) : (SourcesData.Collections.Num() > 0 ? FText::FromName(SourcesData.Collections[0].Name) : LOCTEXT("AllAssets", "All Assets")); HistoryData.HistoryDesc = NewSource; HistoryData.SourcesData = SourcesData; HistoryData.SelectedAssets.Empty(); for ( auto AssetIt = SelectedAssets.CreateConstIterator(); AssetIt; ++AssetIt ) { HistoryData.SelectedAssets.Add(AssetIt->ObjectPath); } } void SContentBrowser::NewAssetRequested(const FString& SelectedPath, TWeakObjectPtr FactoryClass) { if ( ensure(SelectedPath.Len() > 0) && ensure(FactoryClass.IsValid()) ) { UFactory* NewFactory = NewObject(GetTransientPackage(), FactoryClass.Get()); FEditorDelegates::OnConfigureNewAssetProperties.Broadcast(NewFactory); if ( NewFactory->ConfigureProperties() ) { FString DefaultAssetName; FString PackageNameToUse; static FName AssetToolsModuleName = FName("AssetTools"); FAssetToolsModule& AssetToolsModule = FModuleManager::GetModuleChecked(AssetToolsModuleName); AssetToolsModule.Get().CreateUniqueAssetName(SelectedPath + TEXT("/") + NewFactory->GetDefaultNewAssetName(), TEXT(""), PackageNameToUse, DefaultAssetName); CreateNewAsset(DefaultAssetName, SelectedPath, NewFactory->GetSupportedClass(), NewFactory); } } } void SContentBrowser::NewClassRequested(const FString& SelectedPath) { // Parse out the on disk location for the currently selected path, this will then be used as the default location for the new class (if a valid project module location) FString ExistingFolderPath; if (!SelectedPath.IsEmpty()) { TSharedRef NativeClassHierarchy = FContentBrowserSingleton::Get().GetNativeClassHierarchy(); NativeClassHierarchy->GetFileSystemPath(SelectedPath, ExistingFolderPath); } FGameProjectGenerationModule::Get().OpenAddCodeToProjectDialog( FAddToProjectConfig() .InitialPath(ExistingFolderPath) .ParentWindow(FGlobalTabmanager::Get()->GetRootWindow()) ); } void SContentBrowser::NewFolderRequested(const FString& SelectedPath) { if( ensure(SelectedPath.Len() > 0) && AssetViewPtr.IsValid() ) { CreateNewFolder(SelectedPath, FOnCreateNewFolder::CreateSP(AssetViewPtr.Get(), &SAssetView::OnCreateNewFolder)); } } void SContentBrowser::SetSearchBoxText(const FText& InSearchText) { if (!InSearchText.EqualToCaseIgnored(TextFilter->GetRawFilterText())) { TextFilter->SetRawFilterText( InSearchText ); if(InSearchText.IsEmpty()) { FrontendFilters->Remove(TextFilter); AssetViewPtr->SetUserSearching(false); } else { FrontendFilters->Add(TextFilter); AssetViewPtr->SetUserSearching(true); } } } void SContentBrowser::OnSearchBoxChanged(const FText& InSearchText) { SetSearchBoxText(InSearchText); // Broadcast 'search box changed' delegate FContentBrowserModule& ContentBrowserModule = FModuleManager::GetModuleChecked( TEXT("ContentBrowser") ); ContentBrowserModule.GetOnSearchBoxChanged().Broadcast(InSearchText, bIsPrimaryBrowser); } void SContentBrowser::OnSearchBoxCommitted(const FText& InSearchText, ETextCommit::Type CommitInfo) { SetSearchBoxText(InSearchText); } void SContentBrowser::OnPathClicked( const FString& CrumbData ) { FSourcesData SourcesData = AssetViewPtr->GetSourcesData(); if ( SourcesData.Collections.Num() > 0 ) { // Collection crumb was clicked. Since we don't have a hierarchy of collections, this does nothing. } else if ( SourcesData.PackagePaths.Num() == 0 ) { // No collections or paths are selected. This is "All Assets". Don't change the path when this is clicked. } else if ( SourcesData.PackagePaths.Num() > 1 || SourcesData.PackagePaths[0].ToString() != CrumbData ) { // More than one path is selected or the crumb that was clicked is not the same path as the current one. Change the path. TArray SelectedPaths; SelectedPaths.Add(CrumbData); PathViewPtr->SetSelectedPaths(SelectedPaths); SourcesChanged(SelectedPaths, TArray()); } } void SContentBrowser::OnPathMenuItemClicked(FString ClickedPath) { OnPathClicked( ClickedPath ); } TSharedPtr SContentBrowser::OnGetCrumbDelimiterContent(const FString& CrumbData) const { FSourcesData SourcesData = AssetViewPtr->GetSourcesData(); TSharedPtr Widget = SNullWidget::NullWidget; if( SourcesData.PackagePaths.Num() > 0 ) { TArray SubPaths; const bool bRecurse = false; if( ContentBrowserUtils::IsClassPath(CrumbData) ) { TSharedRef NativeClassHierarchy = FContentBrowserSingleton::Get().GetNativeClassHierarchy(); FNativeClassHierarchyFilter ClassFilter; ClassFilter.ClassPaths.Add(FName(*CrumbData)); ClassFilter.bRecursivePaths = bRecurse; NativeClassHierarchy->GetMatchingFolders(ClassFilter, SubPaths); } else { FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked(TEXT("AssetRegistry")); IAssetRegistry& AssetRegistry = AssetRegistryModule.Get(); AssetRegistry.GetSubPaths( CrumbData, SubPaths, bRecurse ); } if( SubPaths.Num() > 0 ) { FMenuBuilder MenuBuilder( true, NULL ); for( int32 PathIndex = 0; PathIndex < SubPaths.Num(); ++PathIndex ) { const FString& SubPath = SubPaths[PathIndex]; // For displaying in the menu cut off the parent path since it is redundant FString PathWithoutParent = SubPath.RightChop( CrumbData.Len() + 1 ); MenuBuilder.AddMenuEntry( FText::FromString(PathWithoutParent), FText::GetEmpty(), FSlateIcon(FEditorStyle::GetStyleSetName(), "ContentBrowser.BreadcrumbPathPickerFolder"), FUIAction(FExecuteAction::CreateSP(this, &SContentBrowser::OnPathMenuItemClicked, SubPath))); } // Do not allow the menu to become too large if there are many directories Widget = SNew( SVerticalBox ) +SVerticalBox::Slot() .MaxHeight( 400.0f ) [ MenuBuilder.MakeWidget() ]; } } return Widget; } TSharedRef SContentBrowser::GetPathPickerContent() { FPathPickerConfig PathPickerConfig; FSourcesData SourcesData = AssetViewPtr->GetSourcesData(); if ( SourcesData.PackagePaths.Num() > 0 ) { PathPickerConfig.DefaultPath = SourcesData.PackagePaths[0].ToString(); } PathPickerConfig.OnPathSelected = FOnPathSelected::CreateSP(this, &SContentBrowser::PathPickerPathSelected); PathPickerConfig.bAllowContextMenu = false; PathPickerConfig.bAllowClassesFolder = true; return SNew(SBox) .WidthOverride(300) .HeightOverride(500) .Padding(4) [ SNew(SVerticalBox) // Path Picker +SVerticalBox::Slot() .FillHeight(1.f) [ FContentBrowserSingleton::Get().CreatePathPicker(PathPickerConfig) ] // Collection View +SVerticalBox::Slot() .AutoHeight() .Padding(0, 6, 0, 0) [ SNew(SCollectionView) .AllowCollectionButtons(false) .OnCollectionSelected(this, &SContentBrowser::PathPickerCollectionSelected) .AllowContextMenu(false) ] ]; } FString SContentBrowser::GetCurrentPath() const { FString CurrentPath; const FSourcesData& SourcesData = AssetViewPtr->GetSourcesData(); if ( SourcesData.PackagePaths.Num() > 0 && SourcesData.PackagePaths[0] != NAME_None ) { CurrentPath = SourcesData.PackagePaths[0].ToString(); } return CurrentPath; } TSharedRef SContentBrowser::MakeAddNewContextMenu(bool bShowGetContent, bool bShowImport) { // Get all menu extenders for this context menu from the content browser module FContentBrowserModule& ContentBrowserModule = FModuleManager::GetModuleChecked( TEXT("ContentBrowser") ); TArray MenuExtenderDelegates = ContentBrowserModule.GetAllAssetContextMenuExtenders(); TArray> Extenders; for (int32 i = 0; i < MenuExtenderDelegates.Num(); ++i) { if (MenuExtenderDelegates[i].IsBound()) { Extenders.Add(MenuExtenderDelegates[i].Execute()); } } TSharedPtr MenuExtender = FExtender::Combine(Extenders); FMenuBuilder MenuBuilder(/*bInShouldCloseWindowAfterMenuSelection=*/true, NULL, MenuExtender); // Only add "New Folder" item if we do not have a collection selected FNewAssetOrClassContextMenu::FOnNewFolderRequested OnNewFolderRequested; if (CollectionViewPtr->GetSelectedCollections().Num() == 0) { OnNewFolderRequested = FNewAssetOrClassContextMenu::FOnNewFolderRequested::CreateSP(this, &SContentBrowser::NewFolderRequested); } const FSourcesData& SourcesData = AssetViewPtr->GetSourcesData(); int32 NumAssetPaths, NumClassPaths; ContentBrowserUtils::CountPathTypes(SourcesData.PackagePaths, NumAssetPaths, NumClassPaths); // New feature packs don't depend on the current paths, so we always add this item if it was requested FNewAssetOrClassContextMenu::FOnGetContentRequested OnGetContentRequested; if(bShowGetContent) { OnGetContentRequested = FNewAssetOrClassContextMenu::FOnGetContentRequested::CreateSP(this, &SContentBrowser::OnAddContentRequested); } // Only the asset items if we have an asset path selected FNewAssetOrClassContextMenu::FOnNewAssetRequested OnNewAssetRequested; FNewAssetOrClassContextMenu::FOnImportAssetRequested OnImportAssetRequested; if(NumAssetPaths > 0) { OnNewAssetRequested = FNewAssetOrClassContextMenu::FOnNewAssetRequested::CreateSP(this, &SContentBrowser::NewAssetRequested); if(bShowImport) { OnImportAssetRequested = FNewAssetOrClassContextMenu::FOnImportAssetRequested::CreateSP(this, &SContentBrowser::ImportAsset); } } // This menu always lets you create classes, but uses your default project source folder if the selected path is invalid for creating classes FNewAssetOrClassContextMenu::FOnNewClassRequested OnNewClassRequested = FNewAssetOrClassContextMenu::FOnNewClassRequested::CreateSP(this, &SContentBrowser::NewClassRequested); FNewAssetOrClassContextMenu::MakeContextMenu( MenuBuilder, SourcesData.PackagePaths, OnNewAssetRequested, OnNewClassRequested, OnNewFolderRequested, OnImportAssetRequested, OnGetContentRequested ); FDisplayMetrics DisplayMetrics; FSlateApplication::Get().GetDisplayMetrics( DisplayMetrics ); const FVector2D DisplaySize( DisplayMetrics.PrimaryDisplayWorkAreaRect.Right - DisplayMetrics.PrimaryDisplayWorkAreaRect.Left, DisplayMetrics.PrimaryDisplayWorkAreaRect.Bottom - DisplayMetrics.PrimaryDisplayWorkAreaRect.Top ); return SNew(SVerticalBox) +SVerticalBox::Slot() .MaxHeight(DisplaySize.Y * 0.5) [ MenuBuilder.MakeWidget() ]; } bool SContentBrowser::IsAddNewEnabled() const { const FSourcesData& SourcesData = AssetViewPtr->GetSourcesData(); return SourcesData.PackagePaths.Num() == 1; } FText SContentBrowser::GetAddNewToolTipText() const { const FSourcesData& SourcesData = AssetViewPtr->GetSourcesData(); if ( SourcesData.PackagePaths.Num() == 1 ) { const FString CurrentPath = SourcesData.PackagePaths[0].ToString(); if ( ContentBrowserUtils::IsClassPath( CurrentPath ) ) { return FText::Format( LOCTEXT("AddNewToolTip_AddNewClass", "Create a new class in {0}..."), FText::FromString(CurrentPath) ); } else { return FText::Format( LOCTEXT("AddNewToolTip_AddNewAsset", "Create a new asset in {0}..."), FText::FromString(CurrentPath) ); } } else if ( SourcesData.PackagePaths.Num() > 1 ) { return LOCTEXT( "AddNewToolTip_MultiplePaths", "Cannot add assets or classes to multiple paths." ); } return LOCTEXT( "AddNewToolTip_NoPath", "No path is selected as an add target." ); } TSharedRef SContentBrowser::MakeAddFilterMenu() { return FilterListPtr->ExternalMakeAddFilterMenu(); } TSharedPtr SContentBrowser::GetFilterContextMenu() { return FilterListPtr->ExternalMakeAddFilterMenu(); } FReply SContentBrowser::OnSaveClicked() { ContentBrowserUtils::SaveDirtyPackages(); return FReply::Handled(); } void SContentBrowser::OnAddContentRequested() { IAddContentDialogModule& AddContentDialogModule = FModuleManager::LoadModuleChecked("AddContentDialog"); FWidgetPath WidgetPath; FSlateApplication::Get().GeneratePathToWidgetChecked(AsShared(), WidgetPath); AddContentDialogModule.ShowDialog(WidgetPath.GetWindow()); } void SContentBrowser::OnAssetSelectionChanged(const FAssetData& SelectedAsset) { if ( bIsPrimaryBrowser ) { SyncGlobalSelectionSet(); } // Notify 'asset selection changed' delegate FContentBrowserModule& ContentBrowserModule = FModuleManager::GetModuleChecked( TEXT("ContentBrowser") ); FContentBrowserModule::FOnAssetSelectionChanged& AssetSelectionChangedDelegate = ContentBrowserModule.GetOnAssetSelectionChanged(); const TArray& SelectedAssets = AssetViewPtr->GetSelectedAssets(); AssetContextMenu->SetSelectedAssets(SelectedAssets); if(AssetSelectionChangedDelegate.IsBound()) { AssetSelectionChangedDelegate.Broadcast(SelectedAssets, bIsPrimaryBrowser); } } void SContentBrowser::OnAssetsActivated(const TArray& ActivatedAssets, EAssetTypeActivationMethod::Type ActivationMethod) { TMap< TSharedRef, TArray > TypeActionsToObjects; TArray ObjectsWithoutTypeActions; const FText LoadingTemplate = LOCTEXT("LoadingAssetName", "Loading {0}..."); const FText DefaultText = ActivatedAssets.Num() == 1 ? FText::Format(LoadingTemplate, FText::FromName(ActivatedAssets[0].AssetName)) : LOCTEXT("LoadingObjects", "Loading Objects..."); FScopedSlowTask SlowTask(100, DefaultText); // Iterate over all activated assets to map them to AssetTypeActions. // This way individual asset type actions will get a batched list of assets to operate on for ( auto AssetIt = ActivatedAssets.CreateConstIterator(); AssetIt; ++AssetIt ) { const FAssetData& AssetData = *AssetIt; if (!AssetData.IsAssetLoaded() && FEditorFileUtils::IsMapPackageAsset(AssetData.ObjectPath.ToString())) { SlowTask.MakeDialog(); } SlowTask.EnterProgressFrame(75.f/ActivatedAssets.Num(), FText::Format(LoadingTemplate, FText::FromName(AssetData.AssetName))); UObject* Asset = (*AssetIt).GetAsset(); if ( Asset != NULL ) { FAssetToolsModule& AssetToolsModule = FModuleManager::Get().LoadModuleChecked("AssetTools"); TWeakPtr AssetTypeActions = AssetToolsModule.Get().GetAssetTypeActionsForClass(Asset->GetClass()); if ( AssetTypeActions.IsValid() ) { // Add this asset to the list associated with the asset type action object TArray& ObjList = TypeActionsToObjects.FindOrAdd(AssetTypeActions.Pin().ToSharedRef()); ObjList.AddUnique(Asset); } else { ObjectsWithoutTypeActions.AddUnique(Asset); } } } // Now that we have created our map, activate all the lists of objects for each asset type action. for ( auto TypeActionsIt = TypeActionsToObjects.CreateConstIterator(); TypeActionsIt; ++TypeActionsIt ) { SlowTask.EnterProgressFrame(25.f/TypeActionsToObjects.Num()); const TSharedRef& TypeActions = TypeActionsIt.Key(); const TArray& ObjList = TypeActionsIt.Value(); TypeActions->AssetsActivated(ObjList, ActivationMethod); } // Finally, open a simple asset editor for all assets which do not have asset type actions if activating with enter or double click if ( ActivationMethod == EAssetTypeActivationMethod::DoubleClicked || ActivationMethod == EAssetTypeActivationMethod::Opened ) { ContentBrowserUtils::OpenEditorForAsset(ObjectsWithoutTypeActions); } } TSharedPtr SContentBrowser::OnGetAssetContextMenu(const TArray& SelectedAssets) { if ( SelectedAssets.Num() == 0 ) { return MakeAddNewContextMenu( false, true ); } else { return AssetContextMenu->MakeContextMenu(SelectedAssets, AssetViewPtr->GetSourcesData(), Commands); } } FReply SContentBrowser::ToggleLockClicked() { bIsLocked = !bIsLocked; return FReply::Handled(); } const FSlateBrush* SContentBrowser::GetToggleLockImage() const { if ( bIsLocked ) { return FEditorStyle::GetBrush("ContentBrowser.LockButton_Locked"); } else { return FEditorStyle::GetBrush("ContentBrowser.LockButton_Unlocked"); } } EVisibility SContentBrowser::GetSourcesViewVisibility() const { return bSourcesViewExpanded ? EVisibility::Visible : EVisibility::Collapsed; } const FSlateBrush* SContentBrowser::GetSourcesToggleImage() const { if ( bSourcesViewExpanded ) { return FEditorStyle::GetBrush("ContentBrowser.HideSourcesView"); } else { return FEditorStyle::GetBrush("ContentBrowser.ShowSourcesView"); } } FReply SContentBrowser::SourcesViewExpandClicked() { bSourcesViewExpanded = !bSourcesViewExpanded; // Notify 'Soureces View Expanded' delegate FContentBrowserModule& ContentBrowserModule = FModuleManager::GetModuleChecked( TEXT("ContentBrowser") ); FContentBrowserModule::FOnSourcesViewChanged& SourcesViewChangedDelegate = ContentBrowserModule.GetOnSourcesViewChanged(); if(SourcesViewChangedDelegate.IsBound()) { SourcesViewChangedDelegate.Broadcast(bSourcesViewExpanded); } return FReply::Handled(); } EVisibility SContentBrowser::GetPathExpanderVisibility() const { return bSourcesViewExpanded ? EVisibility::Collapsed : EVisibility::Visible; } FReply SContentBrowser::BackClicked() { HistoryManager.GoBack(); return FReply::Handled(); } FReply SContentBrowser::ForwardClicked() { HistoryManager.GoForward(); return FReply::Handled(); } bool SContentBrowser::CanRename() const { const TArray>& SelectedItems = AssetViewPtr->GetSelectedItems(); if (SelectedItems.Num() > 0) { return AssetContextMenu->CanExecuteRename(); } else { const TArray& SelectedPaths = PathViewPtr->GetSelectedPaths(); if (SelectedPaths.Num() > 0) { return PathContextMenu->CanExecuteRename(); } } return false; } void SContentBrowser::OnRename() { const TArray>& SelectedItems = AssetViewPtr->GetSelectedItems(); if (SelectedItems.Num() > 0) { AssetContextMenu->ExecuteRename(); } else { const TArray& SelectedPaths = PathViewPtr->GetSelectedPaths(); if (SelectedPaths.Num() > 0) { PathContextMenu->ExecuteRename(); } } } bool SContentBrowser::CanDelete() const { const TArray>& SelectedItems = AssetViewPtr->GetSelectedItems(); if (SelectedItems.Num() > 0) { return AssetContextMenu->CanExecuteDelete(); } else { const TArray& SelectedPaths = PathViewPtr->GetSelectedPaths(); if (SelectedPaths.Num() > 0) { return PathContextMenu->CanExecuteDelete(); } } return false; } void SContentBrowser::OnDelete() { const TArray>& SelectedItems = AssetViewPtr->GetSelectedItems(); if (SelectedItems.Num() > 0) { AssetContextMenu->ExecuteDelete(); } else { const TArray& SelectedPaths = PathViewPtr->GetSelectedPaths(); if (SelectedPaths.Num() > 0) { PathContextMenu->ExecuteDelete(); } } } void SContentBrowser::OnOpenAssetsOrFolders() { AssetViewPtr->OnOpenAssetsOrFolders(); } void SContentBrowser::OnPreviewAssets() { AssetViewPtr->OnPreviewAssets(); } FReply SContentBrowser::OnDirectoryUpClicked() { OnDirectoryUp(); return FReply::Handled(); } void SContentBrowser::OnDirectoryUp() { TArray SelectedPaths = PathViewPtr->GetSelectedPaths(); if(SelectedPaths.Num() == 1 && !ContentBrowserUtils::IsRootDir(SelectedPaths[0])) { FString ParentDir = SelectedPaths[0] / TEXT(".."); FPaths::CollapseRelativeDirectories(ParentDir); FolderEntered(ParentDir); } } bool SContentBrowser::IsBackEnabled() const { return HistoryManager.CanGoBack(); } bool SContentBrowser::IsForwardEnabled() const { return HistoryManager.CanGoForward(); } bool SContentBrowser::CanExecuteDirectoryUp() const { TArray SelectedPaths = PathViewPtr->GetSelectedPaths(); return (SelectedPaths.Num() == 1 && !ContentBrowserUtils::IsRootDir(SelectedPaths[0])); } FText SContentBrowser::GetHistoryBackTooltip() const { if ( HistoryManager.CanGoBack() ) { return FText::Format( LOCTEXT("HistoryBackTooltipFmt", "Back to {0}"), HistoryManager.GetBackDesc() ); } return FText::GetEmpty(); } FText SContentBrowser::GetHistoryForwardTooltip() const { if ( HistoryManager.CanGoForward() ) { return FText::Format( LOCTEXT("HistoryForwardTooltipFmt", "Forward to {0}"), HistoryManager.GetForwardDesc() ); } return FText::GetEmpty(); } FText SContentBrowser::GetDirectoryUpTooltip() const { TArray SelectedPaths = PathViewPtr->GetSelectedPaths(); if(SelectedPaths.Num() == 1 && !ContentBrowserUtils::IsRootDir(SelectedPaths[0])) { FString ParentDir = SelectedPaths[0] / TEXT(".."); FPaths::CollapseRelativeDirectories(ParentDir); return FText::Format(LOCTEXT("DirectoryUpTooltip", "Up to {0}"), FText::FromString(ParentDir) ); } return FText(); } EVisibility SContentBrowser::GetDirectoryUpToolTipVisibility() const { EVisibility ToolTipVisibility = EVisibility::Collapsed; // if we have text in our tooltip, make it visible. if(GetDirectoryUpTooltip().IsEmpty() == false) { ToolTipVisibility = EVisibility::Visible; } return ToolTipVisibility; } void SContentBrowser::SyncGlobalSelectionSet() { USelection* EditorSelection = GEditor->GetSelectedObjects(); if ( !ensure( EditorSelection != NULL ) ) { return; } // Get the selected assets in the asset view const TArray& SelectedAssets = AssetViewPtr->GetSelectedAssets(); EditorSelection->BeginBatchSelectOperation(); { TSet< UObject* > SelectedObjects; // Lets see what the user has selected and add any new selected objects to the global selection set for ( auto AssetIt = SelectedAssets.CreateConstIterator(); AssetIt; ++AssetIt ) { // Grab the object if it is loaded if ( (*AssetIt).IsAssetLoaded() ) { UObject* FoundObject = (*AssetIt).GetAsset(); if( FoundObject != NULL && FoundObject->GetClass() != UObjectRedirector::StaticClass() ) { SelectedObjects.Add( FoundObject ); // Select this object! EditorSelection->Select( FoundObject ); } } } // Now we'll build a list of objects that need to be removed from the global selection set for( int32 CurEditorObjectIndex = 0; CurEditorObjectIndex < EditorSelection->Num(); ++CurEditorObjectIndex ) { UObject* CurEditorObject = EditorSelection->GetSelectedObject( CurEditorObjectIndex ); if( CurEditorObject != NULL ) { if( !SelectedObjects.Contains( CurEditorObject ) ) { EditorSelection->Deselect( CurEditorObject ); } } } } EditorSelection->EndBatchSelectOperation(); } void SContentBrowser::UpdatePath() { FSourcesData SourcesData = AssetViewPtr->GetSourcesData(); PathBreadcrumbTrail->ClearCrumbs(); if ( SourcesData.PackagePaths.Num() > 0 ) { TArray Crumbs; SourcesData.PackagePaths[0].ToString().ParseIntoArray(Crumbs, TEXT("/"), true); FString CrumbPath = TEXT("/"); for ( auto CrumbIt = Crumbs.CreateConstIterator(); CrumbIt; ++CrumbIt ) { // If this is the root part of the path, try and get the localized display name to stay in sync with what we see in SPathView const FText DisplayName = (CrumbIt.GetIndex() == 0) ? ContentBrowserUtils::GetRootDirDisplayName(*CrumbIt) : FText::FromString(*CrumbIt); CrumbPath += *CrumbIt; PathBreadcrumbTrail->PushCrumb(DisplayName, CrumbPath); CrumbPath += TEXT("/"); } } else if ( SourcesData.Collections.Num() > 0 ) { const FString CollectionName = SourcesData.Collections[0].Name.ToString(); const FString CollectionType = FString::FromInt(SourcesData.Collections[0].Type); const FString CrumbData = CollectionName + TEXT("?") + CollectionType; FFormatNamedArguments Args; Args.Add(TEXT("CollectionName"), FText::FromString(CollectionName)); const FText DisplayName = FText::Format(LOCTEXT("CollectionPathIndicator", "{CollectionName} (Collection)"), Args); PathBreadcrumbTrail->PushCrumb(DisplayName, CrumbData); } else { PathBreadcrumbTrail->PushCrumb(LOCTEXT("AllAssets", "All Assets"), TEXT("")); } } void SContentBrowser::OnFilterChanged() { FARFilter Filter = FilterListPtr->GetCombinedBackendFilter(); AssetViewPtr->SetBackendFilter( Filter ); // Notify 'filter changed' delegate FContentBrowserModule& ContentBrowserModule = FModuleManager::GetModuleChecked( TEXT("ContentBrowser") ); ContentBrowserModule.GetOnFilterChanged().Broadcast(Filter, bIsPrimaryBrowser); } FString SContentBrowser::GetPathText() const { FString PathLabelText; if ( IsFilteredBySource() ) { const FSourcesData& SourcesData = AssetViewPtr->GetSourcesData(); // At least one source is selected int32 NumSources = SourcesData.PackagePaths.Num() + SourcesData.Collections.Num(); if (NumSources > 0) { PathLabelText = SourcesData.PackagePaths.Num() > 0 ? SourcesData.PackagePaths[0].ToString() : SourcesData.Collections[0].Name.ToString(); if (NumSources > 1) { PathLabelText += FString::Printf(*LOCTEXT("MultipleSourcesSuffix", " and %d others...").ToString(), NumSources - 1); } } } else { PathLabelText = LOCTEXT("AllAssets", "All Assets").ToString(); } return PathLabelText; } bool SContentBrowser::IsFilteredBySource() const { const FSourcesData& SourcesData = AssetViewPtr->GetSourcesData(); return SourcesData.PackagePaths.Num() != 0 || SourcesData.Collections.Num() != 0; } void SContentBrowser::OnAssetRenameCommitted(const TArray& Assets) { // After a rename is committed we allow an implicit sync so as not to // disorientate the user if they are looking at a parent folder const bool bAllowImplicitSync = true; const bool bDisableFiltersThatHideAssets = false; SyncToAssets(Assets, bAllowImplicitSync, bDisableFiltersThatHideAssets); } void SContentBrowser::OnFindInAssetTreeRequested(const TArray& AssetsToFind) { SyncToAssets(AssetsToFind); } void SContentBrowser::OnRenameRequested(const FAssetData& AssetData) { AssetViewPtr->RenameAsset(AssetData); } void SContentBrowser::OnRenameFolderRequested(const FString& FolderToRename) { const TArray& SelectedFolders = AssetViewPtr->GetSelectedFolders(); if (SelectedFolders.Num() > 0) { AssetViewPtr->RenameFolder(FolderToRename); } else { const TArray& SelectedPaths = PathViewPtr->GetSelectedPaths(); if (SelectedPaths.Num() > 0) { PathViewPtr->RenameFolder(FolderToRename); } } } void SContentBrowser::OnOpenedFolderDeleted() { // Since the contents of the asset view have just been deleted, set the selected path to the default "/Game" TArray DefaultSelectedPaths; DefaultSelectedPaths.Add(TEXT("/Game")); PathViewPtr->SetSelectedPaths(DefaultSelectedPaths); FSourcesData DefaultSourcesData; DefaultSourcesData.PackagePaths.Add(TEXT("/Game")); AssetViewPtr->SetSourcesData(DefaultSourcesData); } void SContentBrowser::OnDuplicateRequested(const TWeakObjectPtr& OriginalObject) { UObject* Object = OriginalObject.Get(); if ( Object ) { AssetViewPtr->DuplicateAsset(FPackageName::GetLongPackagePath(Object->GetOutermost()->GetName()), OriginalObject); } } void SContentBrowser::OnAssetViewRefreshRequested() { AssetViewPtr->RequestSlowFullListRefresh(); } void SContentBrowser::HandleCollectionRemoved(const FCollectionNameType& Collection) { AssetViewPtr->SetSourcesData(FSourcesData()); auto RemoveHistoryDelegate = [&](const FHistoryData& HistoryData) { return (HistoryData.SourcesData.Collections.Num() == 1 && HistoryData.SourcesData.PackagePaths.Num() == 0 && HistoryData.SourcesData.Collections.Contains(Collection)); }; HistoryManager.RemoveHistoryData(RemoveHistoryDelegate); } void SContentBrowser::HandleCollectionRenamed(const FCollectionNameType& OriginalCollection, const FCollectionNameType& NewCollection) { return HandleCollectionRemoved(OriginalCollection); } void SContentBrowser::HandlePathRemoved(const FString& Path) { const FName PathName(*Path); auto RemoveHistoryDelegate = [&](const FHistoryData& HistoryData) { return (HistoryData.SourcesData.PackagePaths.Num() == 1 && HistoryData.SourcesData.Collections.Num() == 0 && HistoryData.SourcesData.PackagePaths.Contains(PathName)); }; HistoryManager.RemoveHistoryData(RemoveHistoryDelegate); } FText SContentBrowser::GetSearchAssetsHintText() const { if (PathViewPtr.IsValid()) { TArray Paths = PathViewPtr->GetSelectedPaths(); if (Paths.Num() != 0) { FString SearchHint = NSLOCTEXT( "ContentBrowser", "SearchBoxPartialHint", "Search" ).ToString(); SearchHint += TEXT(" "); for(int32 i = 0; i < Paths.Num(); i++) { const FString& Path = Paths[i]; if (ContentBrowserUtils::IsRootDir(Path)) { SearchHint += ContentBrowserUtils::GetRootDirDisplayName(Path).ToString(); } else { SearchHint += FPaths::GetCleanFilename(Path); } if (i + 1 < Paths.Num()) { SearchHint += ", "; } } return FText::FromString(SearchHint); } } return NSLOCTEXT( "ContentBrowser", "SearchBoxHint", "Search Assets" ); } TArray SContentBrowser::GetAssetSearchSuggestions() const { TArray AllSuggestions; FAssetToolsModule& AssetToolsModule = FModuleManager::LoadModuleChecked("AssetTools"); TArray< TWeakPtr > AssetTypeActionsList; AssetToolsModule.Get().GetAssetTypeActionsList(AssetTypeActionsList); for ( auto TypeActionsIt = AssetTypeActionsList.CreateConstIterator(); TypeActionsIt; ++TypeActionsIt ) { if ( (*TypeActionsIt).IsValid() ) { const TSharedPtr TypeActions = (*TypeActionsIt).Pin(); AllSuggestions.Add( TypeActions->GetSupportedClass()->GetName() ); } } return AllSuggestions; } TSharedPtr SContentBrowser::GetFolderContextMenu(const TArray& SelectedPaths, FContentBrowserMenuExtender_SelectedPaths InMenuExtender, FOnCreateNewFolder InOnCreateNewFolder, bool bPathView) { // Clear any selection in the asset view, as it'll conflict with other view info // This is important for determining which context menu may be open based on the asset selection for rename/delete operations if (bPathView) { AssetViewPtr->ClearSelection(); } // Ensure the path context menu has the up-to-date list of paths being worked on PathContextMenu->SetSelectedPaths(SelectedPaths); TSharedPtr Extender; if(InMenuExtender.IsBound()) { Extender = InMenuExtender.Execute(SelectedPaths); } const bool bInShouldCloseWindowAfterSelection = true; FMenuBuilder MenuBuilder(bInShouldCloseWindowAfterSelection, Commands, Extender, true); // We can only create folders when we have a single path selected const bool bCanCreateNewFolder = SelectedPaths.Num() == 1 && ContentBrowserUtils::IsValidPathToCreateNewFolder(SelectedPaths[0]); FText NewFolderToolTip; if(SelectedPaths.Num() == 1) { if(bCanCreateNewFolder) { NewFolderToolTip = FText::Format(LOCTEXT("NewFolderTooltip_CreateIn", "Create a new folder in {0}."), FText::FromString(SelectedPaths[0])); } else { NewFolderToolTip = FText::Format(LOCTEXT("NewFolderTooltip_InvalidPath", "Cannot create new folders in {0}."), FText::FromString(SelectedPaths[0])); } } else { NewFolderToolTip = LOCTEXT("NewFolderTooltip_InvalidNumberOfPaths", "Can only create folders when there is a single path selected."); } // New Folder MenuBuilder.AddMenuEntry( LOCTEXT("NewFolder", "New Folder"), NewFolderToolTip, FSlateIcon(FEditorStyle::GetStyleSetName(), "ContentBrowser.NewFolderIcon"), FUIAction( FExecuteAction::CreateSP( this, &SContentBrowser::CreateNewFolder, SelectedPaths.Num() > 0 ? SelectedPaths[0] : FString(), InOnCreateNewFolder ), FCanExecuteAction::CreateLambda( [bCanCreateNewFolder] { return bCanCreateNewFolder; } ) ), "NewFolder" ); return MenuBuilder.MakeWidget(); } void SContentBrowser::CreateNewFolder(FString FolderPath, FOnCreateNewFolder InOnCreateNewFolder) { // Create a valid base name for this folder FText DefaultFolderBaseName = LOCTEXT("DefaultFolderName", "NewFolder"); FText DefaultFolderName = DefaultFolderBaseName; int32 NewFolderPostfix = 1; while(ContentBrowserUtils::DoesFolderExist(FolderPath / DefaultFolderName.ToString())) { DefaultFolderName = FText::Format(LOCTEXT("DefaultFolderNamePattern", "{0}{1}"), DefaultFolderBaseName, FText::AsNumber(NewFolderPostfix)); NewFolderPostfix++; } InOnCreateNewFolder.ExecuteIfBound(DefaultFolderName.ToString(), FolderPath); } #undef LOCTEXT_NAMESPACE