| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034 | // Copyright 2017-2021 marynate. All Rights Reserved.#include "SExtPathView.h"#include "ExtContentBrowser.h"#include "ExtContentBrowserSingleton.h"#include "ExtContentBrowserUtils.h"#include "ExtPackageUtils.h"#include "ExtPathViewTypes.h"#include "ExtSourcesViewWidgets.h"#include "DragDropHandler.h"#include "Adapters/SourcesData.h"#include "Layout/WidgetPath.h"#include "Application/SlateApplicationBase.h"#include "Framework/Application/SlateApplication.h"#include "Widgets/Input/SSearchBox.h"#include "Widgets/Layout/SSeparator.h"#include "EditorStyleSet.h"#include "AssetRegistry/AssetRegistryModule.h"#include "IAssetTools.h"#include "AssetToolsModule.h"#include "DragAndDrop/AssetDragDropOp.h"#include "HAL/FileManager.h"#include "Misc/ConfigCacheIni.h"#if ECB_WIP_HISTORY#include "HistoryManager.h"#endif#define LOCTEXT_NAMESPACE "ExtContentBrowser"SExtPathView::~SExtPathView(){#if ECB_LEGACY	// Unsubscribe from content path events	FPackageName::OnContentPathMounted().RemoveAll( this );	FPackageName::OnContentPathDismounted().RemoveAll( this );	// Unsubscribe from class events	if ( bAllowClassesFolder )	{		TSharedRef<FNativeClassHierarchy> NativeClassHierarchy = FContentBrowserSingleton::Get().GetNativeClassHierarchy();		NativeClassHierarchy->OnClassHierarchyUpdated().RemoveAll( this );	}	// Unsubscribe from folder population events	{		TSharedRef<FEmptyFolderVisibilityManager> EmptyFolderVisibilityManager = FContentBrowserSingleton::Get().GetEmptyFolderVisibilityManager();		EmptyFolderVisibilityManager->OnFolderPopulated().RemoveAll(this);	}	// Load the asset registry module to stop listening for updates	FAssetRegistryModule* AssetRegistryModule = FModuleManager::GetModulePtr<FAssetRegistryModule>(TEXT("AssetRegistry"));	if(AssetRegistryModule)	{		AssetRegistryModule->Get().OnPathAdded().RemoveAll(this);		AssetRegistryModule->Get().OnPathRemoved().RemoveAll(this);		AssetRegistryModule->Get().OnFilesLoaded().RemoveAll(this);	}#endif	FExtContentBrowserSingleton::GetAssetRegistry().OnRootPathAdded().RemoveAll(this);	FExtContentBrowserSingleton::GetAssetRegistry().OnRootPathRemoved().RemoveAll(this);	FExtContentBrowserSingleton::GetAssetRegistry().OnRootPathUpdated().RemoveAll(this);	FExtContentBrowserSingleton::GetAssetRegistry().OnFolderStartGathering().RemoveAll(this);	FExtContentBrowserSingleton::GetAssetRegistry().OnFolderFinishGathering().RemoveAll(this);	SearchBoxFolderFilter->OnChanged().RemoveAll( this );}void SExtPathView::Construct( const FArguments& InArgs ){	OnPathSelected = InArgs._OnPathSelected;	bAllowContextMenu = InArgs._AllowContextMenu;	OnGetFolderContextMenu = InArgs._OnGetFolderContextMenu;	OnGetPathContextMenuExtender = InArgs._OnGetPathContextMenuExtender;	bAllowClassesFolder = InArgs._AllowClassesFolder;	PreventTreeItemChangedDelegateCount = 0;	TreeTitle = LOCTEXT("AssetTreeTitle", "Asset Tree");	PluginVersionText = FExtContentBrowserSingleton::GetPluginVersionText();	if ( InArgs._FocusSearchBoxWhenOpened )	{		RegisterActiveTimer( 0.f, FWidgetActiveTimerDelegate::CreateSP( this, &SExtPathView::SetFocusPostConstruct ) );	}	// Listen for when view settings are changed	UExtContentBrowserSettings::OnSettingChanged().AddSP(this, &SExtPathView::HandleSettingChanged);	//Setup the SearchBox filter	SearchBoxFolderFilter = MakeShareable( new FolderTextFilter( FolderTextFilter::FItemToStringArray::CreateSP( this, &SExtPathView::PopulateFolderSearchStrings ) ) );	SearchBoxFolderFilter->OnChanged().AddSP( this, &SExtPathView::FilterUpdated );	// Listen to find out when new game content paths are mounted or dismounted, so that we can refresh our root set of paths	FPackageName::OnContentPathMounted().AddSP( this, &SExtPathView::OnContentPathMountedOrDismounted );	FPackageName::OnContentPathDismounted().AddSP( this, &SExtPathView::OnContentPathMountedOrDismounted );#if ECB_LEGACY	// Listen to find out when the available classes are changed, so that we can refresh our paths	if ( bAllowClassesFolder )	{		TSharedRef<FNativeClassHierarchy> NativeClassHierarchy = FContentBrowserSingleton::Get().GetNativeClassHierarchy();		NativeClassHierarchy->OnClassHierarchyUpdated().AddSP( this, &SExtPathView::OnClassHierarchyUpdated );	}	// Listen to find out when previously empty paths are populated with content	{		TSharedRef<FEmptyFolderVisibilityManager> EmptyFolderVisibilityManager = FContentBrowserSingleton::Get().GetEmptyFolderVisibilityManager();		EmptyFolderVisibilityManager->OnFolderPopulated().AddSP(this, &SExtPathView::OnFolderPopulated);	}#endif	if (!TreeViewPtr.IsValid())	{		SAssignNew(TreeViewPtr, STreeView< TSharedPtr<FTreeItem> >)			.TreeItemsSource(&TreeRootItems)			.OnGenerateRow(this, &SExtPathView::GenerateTreeRow)			.OnItemScrolledIntoView(this, &SExtPathView::TreeItemScrolledIntoView)			.ItemHeight(18)			.SelectionMode(InArgs._SelectionMode)			.OnSelectionChanged(this, &SExtPathView::TreeSelectionChanged)			.OnExpansionChanged(this, &SExtPathView::TreeExpansionChanged)			.OnGetChildren(this, &SExtPathView::GetChildrenForTree)			.OnSetExpansionRecursive(this, &SExtPathView::SetTreeItemExpansionRecursive)			.OnContextMenuOpening(this, &SExtPathView::MakePathViewContextMenu)			.ClearSelectionOnClick(false)			.HighlightParentNodesForSelection(true);	}	ChildSlot	[		SNew(SVerticalBox)		// Search >>#if ECB_FOLD		+ SVerticalBox::Slot()		.AutoHeight()		.Padding(0, 1, 0, 3)		[			SNew(SHorizontalBox)			+ SHorizontalBox::Slot()			.AutoWidth()			[				InArgs._SearchContent.Widget			]			+ SHorizontalBox::Slot()			.FillWidth(1.0f)			[				SAssignNew(SearchBoxPtr, SSearchBox)				.Visibility(InArgs._SearchBarVisibility)				.HintText( LOCTEXT( "AssetTreeSearchBoxHint", "Search Folders" ) )				.OnTextChanged( this, &SExtPathView::OnAssetTreeSearchBoxChanged )				.OnTextCommitted( this, &SExtPathView::OnAssetTreeSearchBoxCommitted )			]		]#endif	// Search <<		// Tree Section >>#if ECB_FOLD#if 0		// Tree title		+ SVerticalBox::Slot()		.AutoHeight()		.Padding(0)		[			SNew(SBorder)			.BorderBackgroundColor(FLinearColor(1.f, 1.f, 1.f, 1.f))			.BorderImage(FAppStyle::GetBrush("ToolPanel.GroupBorder"))			.Padding(1.f)			.Visibility(this, &SExtPathView::GetTreeTitleVisibility)		]		+SVerticalBox::Slot()		.AutoHeight()		.Padding(2)		[			SNew(SBorder)			.BorderBackgroundColor(FLinearColor(0.f, 0.f, 0.f, 0.1f))			.BorderImage(FAppStyle::GetBrush("ToolPanel.GroupBorder"))			.Padding(4)			.Visibility(this, &SExtPathView::GetTreeTitleVisibility)			[				SNew(SHorizontalBox)				+SHorizontalBox::Slot()				.Padding(4, 0, 0, 0)				[					SNew(STextBlock)					.Font( FAppStyle::GetFontStyle("ContentBrowser.SourceTitleFont") )					//.TextStyle(FAppStyle::Get(), "ContentBrowser.TopBar.Font")					.Text(this, &SExtPathView::GetTreeTitle)					//.Visibility(InArgs._ShowTreeTitle ? EVisibility::Visible : EVisibility::Collapsed)					.Visibility(this, &SExtPathView::GetTreeTitleVisibility)				]			]		]		+ SVerticalBox::Slot()		.AutoHeight()		.Padding(0)		[			SNew(SBorder)			.BorderBackgroundColor(FLinearColor(1.f, 1.f, 1.f, 1.f))			.BorderImage(FAppStyle::GetBrush("ToolPanel.GroupBorder"))			.Padding(1.f)			.Visibility(this, &SExtPathView::GetTreeTitleVisibility)		]#endif		// Separator		+SVerticalBox::Slot()		.AutoHeight()		.Padding(0, 0, 0, 2)		[			SNew(SSeparator)			.Visibility( ( InArgs._ShowSeparator) ? EVisibility::Visible : EVisibility::Collapsed )		]					// Tree		+SVerticalBox::Slot()		.FillHeight(1.f)		[			TreeViewPtr.ToSharedRef()		]		// Tree title		+ SVerticalBox::Slot()		.AutoHeight()		.Padding(0, 2, 0, 0)		[			SNew(SBorder)			.BorderBackgroundColor(FLinearColor(1.f, 1.f, 1.f, 1.f))			.BorderImage(FAppStyle::GetBrush("ToolPanel.GroupBorder"))			.Padding(1.f)			.Visibility(this, &SExtPathView::GetTreeTitleVisibility)		]		+ SVerticalBox::Slot()		.AutoHeight()		.Padding(2)		[			SNew(SBorder)			.BorderBackgroundColor(FLinearColor(0.f, 0.f, 0.f, 0.1f))			.BorderImage(FAppStyle::GetBrush("ToolPanel.GroupBorder"))			.Padding(2)			.Visibility(this, &SExtPathView::GetTreeTitleVisibility)			[				SNew(SHorizontalBox)				+ SHorizontalBox::Slot()				.HAlign(HAlign_Center)				.Padding(4, 0, 0, 0)				[					SNew(STextBlock)					.Font(FAppStyle::GetFontStyle("ContentBrowser.SourceTitleFont"))					.ColorAndOpacity(FLinearColor(1, 1, 1, 0.4f))					.Text(this, &SExtPathView::GetTreeTitle)					.Visibility(this, &SExtPathView::GetTreeTitleVisibility)				]			]		]		+ SVerticalBox::Slot()		.AutoHeight()		.Padding(0)		[			SNew(SBorder)			.BorderBackgroundColor(FLinearColor(1.f, 1.f, 1.f, 1.f))			.BorderImage(FAppStyle::GetBrush("ToolPanel.GroupBorder"))			.Padding(1.f)			.Visibility(this, &SExtPathView::GetTreeTitleVisibility)		]#endif	// Tree Section <<#if ECB_TODO		// Version		+ SVerticalBox::Slot()		.AutoHeight()		.Padding(2, 2, 0, 1)		.HAlign(HAlign_Left)		[			SNew (SHorizontalBox)			+SHorizontalBox::Slot().AutoWidth()			[				SNew(STextBlock).Text(this, &SExtPathView::GetPluginVersionText)				.ColorAndOpacity(FLinearColor::Gray)			]		]#endif	];#if ECB_LEGACY	// Load the asset registry module to listen for updates	FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked<FAssetRegistryModule>(TEXT("AssetRegistry"));	AssetRegistryModule.Get().OnPathAdded().AddSP( this, &SExtPathView::OnAssetRegistryPathAdded );	AssetRegistryModule.Get().OnPathRemoved().AddSP( this, &SExtPathView::OnAssetRegistryPathRemoved );	AssetRegistryModule.Get().OnFilesLoaded().AddSP( this, &SExtPathView::OnAssetRegistrySearchCompleted );#endif	FExtContentBrowserSingleton::GetAssetRegistry().OnRootPathAdded().AddSP(this, &SExtPathView::OnAssetRegistryRootPathAdded);	FExtContentBrowserSingleton::GetAssetRegistry().OnRootPathRemoved().AddSP(this, &SExtPathView::OnAssetRegistryRootPathRemoved);	FExtContentBrowserSingleton::GetAssetRegistry().OnRootPathUpdated().AddSP(this, &SExtPathView::OnAssetRegistryRootPathUpdated);	FExtContentBrowserSingleton::GetAssetRegistry().OnFolderStartGathering().AddSP(this, &SExtPathView::OnAssetRegistryFolderStartGathering);	FExtContentBrowserSingleton::GetAssetRegistry().OnFolderFinishGathering().AddSP(this, &SExtPathView::OnAssetRegistryFolderFinishGathering);	// Add all paths currently gathered from the asset registry	Populate();	// Always expand the game root initially	static const FString GameRootName = TEXT("Game");	for ( auto RootIt = TreeRootItems.CreateConstIterator(); RootIt; ++RootIt )	{		if ( (*RootIt)->FolderName == GameRootName )		{			TreeViewPtr->SetItemExpansion(*RootIt, true);		}	}}void SExtPathView::Tick(const FGeometry& AllottedGeometry, const double InCurrentTime, const float InDeltaTime){#if ECB_FEA_ASYNC_FOLDER_DISCOVERY	if (FExtContentBrowserSingleton::GetAssetRegistry().GetAndTrimFolderGatherResult())	{		Populate();	}#endif}void SExtPathView::SetSelectedPaths(const TArray<FString>& Paths){	if ( !ensure(TreeViewPtr.IsValid()) )	{		return;	}	if ( !SearchBoxPtr->GetText().IsEmpty() )	{		// Clear the search box so the selected paths will be visible		SearchBoxPtr->SetText( FText::GetEmpty() );	}	// Prevent the selection changed delegate since the invoking code requested it	FScopedPreventTreeItemChangedDelegate DelegatePrevention( SharedThis(this) );	// If the selection was changed before all pending initial paths were found, stop attempting to select them	PendingInitialPaths.Empty();	// Clear the selection to start, then add the selected paths as they are found	TreeViewPtr->ClearSelection();	TArray<FString> RootContentPaths;	FExtContentBrowserSingleton::GetAssetRegistry().QueryRootContentPaths(RootContentPaths);	for (int32 PathIdx = 0; PathIdx < Paths.Num(); ++PathIdx)	{		const FString& Path = Paths[PathIdx];		TArray<FString> PathItemList;		GetPathItemList(Path, RootContentPaths, PathItemList, /*bIncludeRootPath*/true);		if ( PathItemList.Num() )		{			// There is at least one element in the path			TArray<TSharedPtr<FTreeItem>> TreeItems;			// Find the first item in the root items list			for ( int32 RootItemIdx = 0; RootItemIdx < TreeRootItems.Num(); ++RootItemIdx )			{				if ( TreeRootItems[RootItemIdx]->FolderPath == PathItemList[0] )				{					// Found the first item in the path					TreeItems.Add(TreeRootItems[RootItemIdx]);					break;				}			}			// If found in the root items list, try to find the childmost item matching the path			if ( TreeItems.Num() > 0 )			{				for ( int32 PathItemIdx = 1; PathItemIdx < PathItemList.Num(); ++PathItemIdx )				{					const FString& PathItemName = PathItemList[PathItemIdx];					const TSharedPtr<FTreeItem> ChildItem = TreeItems.Last()->GetChild(PathItemName);					if ( ChildItem.IsValid() )					{						// Update tree items list						TreeItems.Add(ChildItem);					}					else					{						// Could not find the child item						break;					}				}				// Expand all the tree folders up to but not including the last one.				for (int32 ItemIdx = 0; ItemIdx < TreeItems.Num() - 1; ++ItemIdx)				{					TreeViewPtr->SetItemExpansion(TreeItems[ItemIdx], true);				}				// Set the selection to the closest found folder and scroll it into view				TreeViewPtr->SetItemSelection(TreeItems.Last(), true);				TreeViewPtr->RequestScrollIntoView(TreeItems.Last());			}			else			{				// Could not even find the root path... skip			}		}		else		{			// No path items... skip		}	}}void SExtPathView::ClearSelection(){	// Prevent the selection changed delegate since the invoking code requested it	FScopedPreventTreeItemChangedDelegate DelegatePrevention( SharedThis(this) );	// If the selection was changed before all pending initial paths were found, stop attempting to select them	PendingInitialPaths.Empty();	// Clear the selection to start, then add the selected paths as they are found	TreeViewPtr->ClearSelection();}int32 SExtPathView::GetNumPathsSelected() const{	return TreeViewPtr->GetNumItemsSelected();}FString SExtPathView::GetSelectedPath() const{	TArray<TSharedPtr<FTreeItem>> Items = TreeViewPtr->GetSelectedItems();	if (Items.Num() > 0)	{		FString& FolderPath = Items[0]->FolderPath;		return FolderPath;	}	return FString();}TArray<FString> SExtPathView::GetSelectedPaths() const{	TArray<FString> RetArray;	TArray<TSharedPtr<FTreeItem>> Items = TreeViewPtr->GetSelectedItems();	for (int32 ItemIdx = 0; ItemIdx < Items.Num(); ++ItemIdx)	{		FString& FolderPath = Items[ItemIdx]->FolderPath;		RetArray.Add(FolderPath);	}	return RetArray;}TSharedPtr<FTreeItem> SExtPathView::AddPath(const FString& InPath, const FString* RootPathPtr/* = nullptr*/, bool bUserNamed){	if ( !ensure(TreeViewPtr.IsValid()) )	{		// No tree view for some reason		return TSharedPtr<FTreeItem>();	}	FString Path = InPath;	FString RootPath;	if (RootPathPtr != nullptr)	{		RootPath = *RootPathPtr;	}	TArray<FString> RootContentPaths;	FExtContentBrowserSingleton::GetAssetRegistry().QueryRootContentPaths(RootContentPaths);	TArray<FString> PathItemList;	GetPathItemList(Path, /*{ RootPath }*/RootContentPaths, PathItemList, /*bIncludeRootPath*/ true);	if ( PathItemList.Num() )	{		FString ContentRootPath = PathItemList[0];		// There is at least one element in the path		TSharedPtr<FTreeItem> CurrentItem;		// Find the first item in the root items list		for ( int32 RootItemIdx = 0; RootItemIdx < TreeRootItems.Num(); ++RootItemIdx )		{			if ( TreeRootItems[RootItemIdx]->FolderPath == ContentRootPath)			{				// Found the first item in the path				CurrentItem = TreeRootItems[RootItemIdx];				break;			}		}		// Roots may or may not exist, add the root here if it doesn't		if ( !CurrentItem.IsValid() )		{			CurrentItem = AddRootItem(ContentRootPath);		}		// Found or added the root item?		if ( CurrentItem.IsValid() )		{			// Now add children as necessary			const bool bDisplayEmpty = GetDefault<UExtContentBrowserSettings>()->DisplayEmptyFolders;			const bool bDisplayDev = GetDefault<UExtContentBrowserSettings>()->GetDisplayDevelopersFolder();			const bool bDisplayL10N = GetDefault<UExtContentBrowserSettings>()->GetDisplayL10NFolder();			for ( int32 PathItemIdx = 1; PathItemIdx < PathItemList.Num(); ++PathItemIdx )			{				const FString& PathItemName = PathItemList[PathItemIdx];				TSharedPtr<FTreeItem> ChildItem = CurrentItem->GetChild(PathItemName);				// If it does not exist, Create the child item				if ( !ChildItem.IsValid() )				{					const FString FolderName = PathItemName;					const FString FolderPath = CurrentItem->FolderPath + "/" + PathItemName;					ChildItem = MakeShareable( new FTreeItem(FText::FromString(FolderName), FolderName, FolderPath, RootPath, CurrentItem, bUserNamed) );					CurrentItem->Children.Add(ChildItem);					CurrentItem->RequestSortChildren();					TreeViewPtr->RequestTreeRefresh();					// If we have pending initial paths, and this path added the path, we should select it now					if ( PendingInitialPaths.Num() > 0 && PendingInitialPaths.Contains(FolderPath) )					{						RecursiveExpandParents(ChildItem);						TreeViewPtr->SetItemSelection(ChildItem, true);						TreeViewPtr->RequestScrollIntoView(ChildItem);					}				}				else				{					//If the child item does exist, ensure its folder path is correct (may differ when renaming parent folder)					ChildItem->FolderPath = CurrentItem->FolderPath + "/" + PathItemName;				}				CurrentItem = ChildItem;			}			if ( bUserNamed && CurrentItem->Parent.IsValid() )			{				// If we were creating a new item, select it, scroll it into view, expand the parent				RecursiveExpandParents(CurrentItem);				TreeViewPtr->RequestScrollIntoView(CurrentItem);				TreeViewPtr->SetSelection(CurrentItem);			}			else			{				CurrentItem->bNamingFolder = false;			}			// Root: Is Loading			if (!CurrentItem->Parent.IsValid())			{				const bool bIsLoading = ExtContentBrowserUtils::IsFolderBackgroundGathering(CurrentItem->FolderPath);				CurrentItem->bLoading = bIsLoading;			}						// Update Root's loading status			if (CurrentItem->Parent.IsValid())			{				TSharedPtr<FTreeItem> RootOfCurrentItem = FindItemRecursive(RootPath);				if (RootOfCurrentItem.IsValid())				{					const bool bIsLoading = ExtContentBrowserUtils::IsFolderBackgroundGathering(RootPath);					RootOfCurrentItem->bLoading = bIsLoading;					if (bIsLoading)					{						FName GatheringFolder = ExtContentBrowserUtils::GetCurrentGatheringFolder();						if (GatheringFolder != NAME_None)						{							FString LoadingFolder = ExtContentBrowserUtils::GetCurrentGatheringFolder().ToString();							if (LoadingFolder.StartsWith(RootPath))							{								LoadingFolder.RemoveFromStart(RootPath);								LoadingFolder.RemoveFromStart(TEXT("/"));								RootOfCurrentItem->LoadingStatus = LoadingFolder;							}						}					}				}			}		}		return CurrentItem;	}	return TSharedPtr<FTreeItem>();}bool SExtPathView::RemovePath(const FString& Path){	if ( !ensure(TreeViewPtr.IsValid()) )	{		// No tree view for some reason		return false;	}	if ( Path.IsEmpty() )	{		// There were no elements in the path, cannot remove nothing		return false;	}	// Find the folder in the tree	TSharedPtr<FTreeItem> ItemToRemove = FindItemRecursive(Path);	if ( ItemToRemove.IsValid() )	{		// Found the folder to remove. Remove it.		if ( ItemToRemove->Parent.IsValid() )		{			// Remove the folder from its parent's list			ItemToRemove->Parent.Pin()->Children.Remove(ItemToRemove);		}		else		{			// This is a root item. Remove the folder from the root items list.			TreeRootItems.Remove(ItemToRemove);		}		// Refresh the tree		TreeViewPtr->RequestTreeRefresh();		return true;	}	else	{		// Did not find the folder to remove		return false;	}}void SExtPathView::RenameFolder(const FString& FolderToRename){	TArray<TSharedPtr<FTreeItem>> Items = TreeViewPtr->GetSelectedItems();	for (int32 ItemIdx = 0; ItemIdx < Items.Num(); ++ItemIdx)	{		TSharedPtr<FTreeItem>& Item = Items[ItemIdx];		if (Item.IsValid())		{			if (Item->FolderPath == FolderToRename)			{				Item->bNamingFolder = true;				TreeViewPtr->SetSelection(Item);				TreeViewPtr->RequestScrollIntoView(Item);				break;			}		}	}}void SExtPathView::SyncToAssets( const TArray<FExtAssetData>& AssetDataList, const bool bAllowImplicitSync ){	SyncToInternal(AssetDataList, TArray<FString>(), bAllowImplicitSync);}void SExtPathView::SyncToFolders( const TArray<FString>& FolderList, const bool bAllowImplicitSync ){	SyncToInternal(TArray<FExtAssetData>(), FolderList, bAllowImplicitSync);}void SExtPathView::SyncTo( const FExtContentBrowserSelection& ItemSelection, const bool bAllowImplicitSync ){	SyncToInternal(ItemSelection.SelectedAssets, ItemSelection.SelectedFolders, bAllowImplicitSync);}void SExtPathView::SyncToInternal( const TArray<FExtAssetData>& AssetDataList, const TArray<FString>& FolderPaths, const bool bAllowImplicitSync ){	TArray<TSharedPtr<FTreeItem>> SyncTreeItems;	// Clear the filter	SearchBoxPtr->SetText(FText::GetEmpty());	TSet<FString> PackagePaths = TSet<FString>(FolderPaths);	for (const FExtAssetData& AssetData : AssetDataList)	{		FString PackagePath = AssetData.GetFolderPath();		PackagePaths.Add(PackagePath);	}	for (const FString& PackagePath : PackagePaths)	{		if ( !PackagePath.IsEmpty() )		{			TSharedPtr<FTreeItem> Item = FindItemRecursive(PackagePath);			if ( Item.IsValid() )			{				SyncTreeItems.Add(Item);			}		}	}	if ( SyncTreeItems.Num() > 0 )	{		if (bAllowImplicitSync)		{			// Prune the current selection so that we don't unnecessarily change the path which might disorientate the user.			// If a parent tree item is currently selected we don't need to clear it and select the child			auto SelectedTreeItems = TreeViewPtr->GetSelectedItems();			for (int32 Index = 0; Index < SelectedTreeItems.Num(); ++Index)			{				// For each item already selected in the tree				auto AlreadySelectedTreeItem = SelectedTreeItems[Index];				if (!AlreadySelectedTreeItem.IsValid())				{					continue;				}				// Check to see if any of the items to sync are already synced				for (int32 ToSyncIndex = SyncTreeItems.Num()-1; ToSyncIndex >= 0; --ToSyncIndex)				{					auto ToSyncItem = SyncTreeItems[ToSyncIndex];					if (ToSyncItem == AlreadySelectedTreeItem || ToSyncItem->IsChildOf(*AlreadySelectedTreeItem.Get()))					{						// A parent is already selected						SyncTreeItems.Pop();					}					else if (ToSyncIndex == 0)					{						// AlreadySelectedTreeItem is not required for SyncTreeItems, so deselect it						TreeViewPtr->SetItemSelection(AlreadySelectedTreeItem, false);					}				}			}		}		else		{			// Explicit sync so just clear the selection			TreeViewPtr->ClearSelection();		}		// SyncTreeItems should now only contain items which aren't already shown explicitly or implicitly (as a child)		for ( auto ItemIt = SyncTreeItems.CreateConstIterator(); ItemIt; ++ItemIt )		{			RecursiveExpandParents(*ItemIt);			TreeViewPtr->SetItemSelection(*ItemIt, true);		}		// > 0 as some may have been popped off in the code above		if (SyncTreeItems.Num() > 0)		{			// Scroll the first item into view if applicable			TreeViewPtr->RequestScrollIntoView(SyncTreeItems[0]);		}	}}TSharedPtr<FTreeItem> SExtPathView::FindItemRecursive(const FString& Path) const{	for (auto TreeItemIt = TreeRootItems.CreateConstIterator(); TreeItemIt; ++TreeItemIt)	{		if ( (*TreeItemIt)->FolderPath == Path)		{			// This root item is the path			return *TreeItemIt;		}		// Try to find the item under this root		TSharedPtr<FTreeItem> Item = (*TreeItemIt)->FindItemRecursive(Path);		if ( Item.IsValid() )		{			// The item was found under this root			return Item;		}	}	return TSharedPtr<FTreeItem>();}void SExtPathView::ApplyHistoryData ( const FHistoryData& History ){#if ECB_WIP_HISTORY	// Prevent the selection changed delegate because it would add more history when we are just setting a state	FScopedPreventTreeItemChangedDelegate DelegatePrevention( SharedThis(this) );	// Update paths	TArray<FString> SelectedPaths;	for (const FName& HistoryPath : History.SourcesData.PackagePaths)	{		SelectedPaths.Add(HistoryPath.ToString());	}	SetSelectedPaths(SelectedPaths);#endif}void SExtPathView::SaveSettings(const FString& IniFilename, const FString& IniSection, const FString& SettingsString) const{	FString SelectedPathsString;	TArray< TSharedPtr<FTreeItem> > PathItems = TreeViewPtr->GetSelectedItems();	for ( auto PathIt = PathItems.CreateConstIterator(); PathIt; ++PathIt )	{		if ( SelectedPathsString.Len() > 0 )		{			SelectedPathsString += TEXT(",");		}		SelectedPathsString += (*PathIt)->FolderPath;	}	GConfig->SetString(*IniSection, *(SettingsString + TEXT(".SelectedPaths")), *SelectedPathsString, IniFilename);}void SExtPathView::LoadSettings(const FString& IniFilename, const FString& IniSection, const FString& SettingsString){	// Selected Paths	FString SelectedPathsString;	TArray<FString> NewSelectedPaths;	if ( GConfig->GetString(*IniSection, *(SettingsString + TEXT(".SelectedPaths")), SelectedPathsString, IniFilename) )	{		SelectedPathsString.ParseIntoArray(NewSelectedPaths, TEXT(","), /*bCullEmpty*/true);	}	if ( NewSelectedPaths.Num() > 0 )	{		FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked<FAssetRegistryModule>("AssetRegistry");		const bool bDiscoveringAssets = AssetRegistryModule.Get().IsLoadingAssets();		if ( bDiscoveringAssets )		{			// Keep track if we changed at least one source so we know to fire the bulk selection changed delegate later			bool bSelectedAtLeastOnePath = false;			{				// Prevent the selection changed delegate since we are selecting one path at a time. A bulk event will be fired later if needed.				FScopedPreventTreeItemChangedDelegate DelegatePrevention( SharedThis(this) );								// Clear any previously selected paths				TreeViewPtr->ClearSelection();				// If the selected paths is empty, the path was "All assets"				// This should handle that case properly				for (int32 PathIdx = 0; PathIdx < NewSelectedPaths.Num(); ++PathIdx)				{					const FString& Path = NewSelectedPaths[PathIdx];					if ( ExplicitlyAddPathToSelection(Path) )					{						bSelectedAtLeastOnePath = true;					}					else					{						// If we could not initially select these paths, but are still discovering assets, add them to a pending list to select them later						PendingInitialPaths.Add(Path);					}				}			}			if ( bSelectedAtLeastOnePath )			{				// Send the first selected item with the notification				const TArray<TSharedPtr<FTreeItem>> SelectedItems = TreeViewPtr->GetSelectedItems();				check(SelectedItems.Num() > 0);				// Signal a single selection changed event to let any listeners know that paths have changed				TreeSelectionChanged( SelectedItems[0], ESelectInfo::Direct );			}		}		else		{			// If all assets are already discovered, just select paths the best we can			SetSelectedPaths(NewSelectedPaths);			// Send the first selected item with the notification			const TArray<TSharedPtr<FTreeItem>> SelectedItems = TreeViewPtr->GetSelectedItems();			if (SelectedItems.Num() > 0)			{				// Signal a single selection changed event to let any listeners know that paths have changed				TreeSelectionChanged( SelectedItems[0], ESelectInfo::Direct );			}		}	}}EActiveTimerReturnType SExtPathView::SetFocusPostConstruct( double InCurrentTime, float InDeltaTime ){	FWidgetPath WidgetToFocusPath;	FSlateApplication::Get().GeneratePathToWidgetUnchecked( SearchBoxPtr.ToSharedRef(), WidgetToFocusPath );	FSlateApplication::Get().SetKeyboardFocus( WidgetToFocusPath, EFocusCause::SetDirectly );	return EActiveTimerReturnType::Stop;}EActiveTimerReturnType SExtPathView::TriggerRepopulate(double InCurrentTime, float InDeltaTime){	Populate();	return EActiveTimerReturnType::Stop;}TSharedPtr<SWidget> SExtPathView::MakePathViewContextMenu(){	if (!bAllowContextMenu || !OnGetFolderContextMenu.IsBound())	{		return nullptr;	}	const TArray<FString> SelectedPaths = GetSelectedPaths();	if (SelectedPaths.Num() == 0)	{		return nullptr;	}	return OnGetFolderContextMenu.Execute(SelectedPaths, OnGetPathContextMenuExtender, NULL);}void SExtPathView::OnCreateNewFolder(const FString& FolderName, const FString& FolderPath){	AddPath(FolderPath / FolderName);}bool SExtPathView::ExplicitlyAddPathToSelection(const FString& Path){	if ( !ensure(TreeViewPtr.IsValid()) )	{		return false;	}	TArray<FString> PathItemList;	Path.ParseIntoArray(PathItemList, TEXT("/"), /*InCullEmpty=*/true);	if ( PathItemList.Num() )	{		// There is at least one element in the path		TSharedPtr<FTreeItem> RootItem;		// Find the first item in the root items list		for ( int32 RootItemIdx = 0; RootItemIdx < TreeRootItems.Num(); ++RootItemIdx )		{			if ( TreeRootItems[RootItemIdx]->FolderPath == PathItemList[0] )			{				// Found the first item in the path				RootItem = TreeRootItems[RootItemIdx];				break;			}		}		// If found in the root items list, try to find the item matching the path		if ( RootItem.IsValid() )		{			TSharedPtr<FTreeItem> FoundItem = RootItem->FindItemRecursive(Path);			if ( FoundItem.IsValid() )			{				// Set the selection to the closest found folder and scroll it into view				RecursiveExpandParents(FoundItem);				TreeViewPtr->SetItemSelection(FoundItem, true);				TreeViewPtr->RequestScrollIntoView(FoundItem);				return true;			}		}	}	return false;}bool SExtPathView::ShouldAllowTreeItemChangedDelegate() const{	return PreventTreeItemChangedDelegateCount == 0;}void SExtPathView::RecursiveExpandParents(const TSharedPtr<FTreeItem>& Item){	if ( Item->Parent.IsValid() )	{		RecursiveExpandParents(Item->Parent.Pin());		TreeViewPtr->SetItemExpansion(Item->Parent.Pin(), true);	}}TSharedPtr<struct FTreeItem> SExtPathView::AddRootItem( const FString& InFolderName ){	// Make sure the item is not already in the list	for ( int32 RootItemIdx = 0; RootItemIdx < TreeRootItems.Num(); ++RootItemIdx )	{		if ( TreeRootItems[RootItemIdx]->FolderName == InFolderName )		{			// The root to add was already in the list return it here			return TreeRootItems[RootItemIdx];		}	}	TSharedPtr<struct FTreeItem> NewItem = nullptr;	const bool bIsValidRootFolder = true;	if (bIsValidRootFolder)	{		const FText DisplayName = ExtContentBrowserUtils::GetRootDirDisplayName(InFolderName);		NewItem = MakeShareable( new FTreeItem(DisplayName, /*FolderName*/ InFolderName, /*InFolderPath*/ InFolderName, /*InRootFolderPath*/InFolderName, TSharedPtr<FTreeItem>()));		TreeRootItems.Add( NewItem );		TreeViewPtr->RequestTreeRefresh();	}	return NewItem;}TSharedRef<ITableRow> SExtPathView::GenerateTreeRow( TSharedPtr<FTreeItem> TreeItem, const TSharedRef<STableViewBase>& OwnerTable ){	check(TreeItem.IsValid());	return		SNew( STableRow< TSharedPtr<FTreeItem> >, OwnerTable )		.OnDragDetected( this, &SExtPathView::OnFolderDragDetected )		[			SNew(SExtAssetTreeItem)			.TreeItem(TreeItem)			.OnAssetsOrPathsDragDropped(this, &SExtPathView::TreeAssetsOrPathsDropped)			.OnFilesDragDropped(this, &SExtPathView::TreeFilesDropped)			.IsItemExpanded(this, &SExtPathView::IsTreeItemExpanded, TreeItem)			.HighlightText(this, &SExtPathView::GetHighlightText)			.IsSelected(this, &SExtPathView::IsTreeItemSelected, TreeItem)		];}void SExtPathView::TreeItemScrolledIntoView( TSharedPtr<FTreeItem> TreeItem, const TSharedPtr<ITableRow>& Widget ){	if ( TreeItem->bNamingFolder && Widget.IsValid() && Widget->GetContent().IsValid() )	{		TreeItem->OnRenamedRequestEvent.Broadcast();	}}void SExtPathView::GetChildrenForTree( TSharedPtr< FTreeItem > TreeItem, TArray< TSharedPtr<FTreeItem> >& OutChildren ){	TreeItem->SortChildrenIfNeeded();	OutChildren = TreeItem->Children;}void SExtPathView::SetTreeItemExpansionRecursive( TSharedPtr< FTreeItem > TreeItem, bool bInExpansionState ){	TreeViewPtr->SetItemExpansion(TreeItem, bInExpansionState);	// Recursively go through the children.	for(auto It = TreeItem->Children.CreateIterator(); It; ++It)	{		SetTreeItemExpansionRecursive( *It, bInExpansionState );	}}void SExtPathView::TreeSelectionChanged( TSharedPtr< FTreeItem > TreeItem, ESelectInfo::Type /*SelectInfo*/ ){	if ( ShouldAllowTreeItemChangedDelegate() )	{		const TArray<TSharedPtr<FTreeItem>> SelectedItems = TreeViewPtr->GetSelectedItems();		LastSelectedPaths.Empty();		for (int32 ItemIdx = 0; ItemIdx < SelectedItems.Num(); ++ItemIdx)		{			const TSharedPtr<FTreeItem> Item = SelectedItems[ItemIdx];			if ( !ensure(Item.IsValid()) )			{				// All items must exist				continue;			}			// Keep track of the last paths that we broadcasted for selection reasons when filtering			LastSelectedPaths.Add(Item->FolderPath);		}		if (OnPathSelected.IsBound() )		{			if ( TreeItem.IsValid() )			{				OnPathSelected.Execute(TreeItem->FolderPath);			}			else			{				OnPathSelected.Execute(TEXT(""));			}		}	}	if (TreeItem.IsValid())	{		// Prioritize the asset registry scan for the selected path		FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked<FAssetRegistryModule>(TEXT("AssetRegistry"));		AssetRegistryModule.Get().PrioritizeSearchPath(TreeItem->FolderPath / TEXT(""));	}}void SExtPathView::TreeExpansionChanged( TSharedPtr< FTreeItem > TreeItem, bool bIsExpanded ){	if ( ShouldAllowTreeItemChangedDelegate() )	{		TSet<TSharedPtr<FTreeItem>> ExpandedItemSet;		TreeViewPtr->GetExpandedItems(ExpandedItemSet);		const TArray<TSharedPtr<FTreeItem>> ExpandedItems = ExpandedItemSet.Array();		LastExpandedPaths.Empty();		for (int32 ItemIdx = 0; ItemIdx < ExpandedItems.Num(); ++ItemIdx)		{			const TSharedPtr<FTreeItem> Item = ExpandedItems[ItemIdx];			if ( !ensure(Item.IsValid()) )			{				// All items must exist				continue;			}			// Keep track of the last paths that we broadcasted for expansion reasons when filtering			LastExpandedPaths.Add(Item->FolderPath);		}	}}void SExtPathView::OnAssetTreeSearchBoxChanged( const FText& InSearchText ){	SearchBoxFolderFilter->SetRawFilterText( InSearchText );	SearchBoxPtr->SetError( SearchBoxFolderFilter->GetFilterErrorText() );}void SExtPathView::OnAssetTreeSearchBoxCommitted(const FText& InSearchText, ETextCommit::Type InCommitType){	if (InCommitType == ETextCommit::OnCleared)	{		// Clear the search box and the filters		SearchBoxPtr->SetText(FText::GetEmpty());		OnAssetTreeSearchBoxChanged(FText::GetEmpty());		FSlateApplication::Get().ClearKeyboardFocus(EFocusCause::Cleared);	}}void SExtPathView::FilterUpdated(){	Populate();}FText SExtPathView::GetHighlightText() const{	return SearchBoxFolderFilter->GetRawFilterText();}void SExtPathView::Populate(){	ECB_LOG(Display, TEXT("[SExtPathView] Populate"));	// Don't allow the selection changed delegate to be fired here	FScopedPreventTreeItemChangedDelegate DelegatePrevention( SharedThis(this) );	// Clear all root items and clear selection	TreeRootItems.Empty();	TreeViewPtr->ClearSelection();	const bool bFilteringByText = !SearchBoxFolderFilter->GetRawFilterText().IsEmpty();	TArray<FString> RootContentPaths;	FExtContentBrowserSingleton::GetAssetRegistry().QueryRootContentPaths(RootContentPaths);	// Add default root folders to the asset tree if there's no filtering	if ( !bFilteringByText ) 	{			for( TArray<FString>::TConstIterator RootPathIt( RootContentPaths ); RootPathIt; ++RootPathIt )		{			// Strip off any trailing forward slashes			FString CleanRootPathName = *RootPathIt;			while( CleanRootPathName.EndsWith( TEXT( "/" ) ) )			{				CleanRootPathName = CleanRootPathName.Mid( 0, CleanRootPathName.Len() - 1 );			}			AddPath(CleanRootPathName, &CleanRootPathName);		}	}		for (const FString& RootContentPath : RootContentPaths)	{		if (FExtContentBrowserSingleton::GetAssetRegistry().IsFolderBackgroundGathering(*RootContentPath))		{			//continue;		}		// Get all paths		TSet<FName> SubPaths;		FExtContentBrowserSingleton::GetAssetRegistry().GetCachedSubPaths/*GetOrCacheSubPaths*//*safer*/(*RootContentPath, SubPaths, /*bRecursively = */ true);		// Add all paths		for (const FName& SubPath : SubPaths)		{			const FString Path = SubPath.ToString();			// by sending the whole path we deliberately include any children of successful hits in the filtered list. 			if (SearchBoxFolderFilter->PassesFilter(Path))			{				TSharedPtr<FTreeItem> Item = AddPath(Path, &RootContentPath);				if (Item.IsValid())				{					const bool bSelectedItem = LastSelectedPaths.Contains(Item->FolderPath);					const bool bExpandedItem = LastExpandedPaths.Contains(Item->FolderPath);					if (bFilteringByText || bSelectedItem)					{						RecursiveExpandParents(Item);					}					if (bSelectedItem)					{						// Tree items that match the last broadcasted paths should be re-selected them after they are added						if (!TreeViewPtr->IsItemSelected(Item))						{							TreeViewPtr->SetItemSelection(Item, true);						}						TreeViewPtr->RequestScrollIntoView(Item);					}					if (bExpandedItem)					{						// Tree items that were previously expanded should be re-expanded when repopulating						if (!TreeViewPtr->IsItemExpanded(Item))						{							TreeViewPtr->SetItemExpansion(Item, true);							//RecursiveExpandParents(Item);						}					}				}			}		}	}	SortRootItems();}void SExtPathView::SortRootItems(){	// First sort the root items by their display name, but also making sure that content to appears before classes	TreeRootItems.Sort([](const TSharedPtr<FTreeItem>& One, const TSharedPtr<FTreeItem>& Two) -> bool	{		static const FString ClassesPrefix = TEXT("Classes_");		FString OneModuleName = One->FolderName;		const bool bOneIsClass = OneModuleName.StartsWith(ClassesPrefix);		if(bOneIsClass)		{			OneModuleName = OneModuleName.Mid(ClassesPrefix.Len());		}		FString TwoModuleName = Two->FolderName;		const bool bTwoIsClass = TwoModuleName.StartsWith(ClassesPrefix);		if(bTwoIsClass)		{			TwoModuleName = TwoModuleName.Mid(ClassesPrefix.Len());		}		// We want to sort content before classes if both items belong to the same module		if(OneModuleName == TwoModuleName)		{			if(!bOneIsClass && bTwoIsClass)			{				return true;			}			return false;		}		return One->DisplayName.ToString() < Two->DisplayName.ToString();	});	// We have some manual sorting requirements that game must come before engine, and engine before everything else - we do that here after sorting everything by name	// The array below is in the inverse order as we iterate through and move each match to the beginning of the root items array	static const FString InverseSortOrder[] = {		TEXT("Classes_Engine"),		TEXT("Engine"),		TEXT("Classes_Game"),		TEXT("Game"),	};	for(const FString& SortItem : InverseSortOrder)	{		const int32 FoundItemIndex = TreeRootItems.IndexOfByPredicate([&SortItem](const TSharedPtr<FTreeItem>& TreeItem) -> bool		{			return TreeItem->FolderName == SortItem;		});		if(FoundItemIndex != INDEX_NONE)		{			TSharedPtr<FTreeItem> ItemToMove = TreeRootItems[FoundItemIndex];			TreeRootItems.RemoveAt(FoundItemIndex);			TreeRootItems.Insert(ItemToMove, 0);		}	}	TreeViewPtr->RequestTreeRefresh();}void SExtPathView::PopulateFolderSearchStrings( const FString& FolderName, OUT TArray< FString >& OutSearchStrings ) const{	OutSearchStrings.Add( FolderName );}FReply SExtPathView::OnFolderDragDetected(const FGeometry& Geometry, const FPointerEvent& MouseEvent){	if ( MouseEvent.IsMouseButtonDown( EKeys::LeftMouseButton ) )	{		TArray<TSharedPtr<FTreeItem>> SelectedItems = TreeViewPtr->GetSelectedItems();		if (SelectedItems.Num())		{			TArray<FString> PathNames;			for ( auto ItemIt = SelectedItems.CreateConstIterator(); ItemIt; ++ItemIt )			{				PathNames.Add((*ItemIt)->FolderPath);			}			return FReply::Handled().BeginDragDrop(FAssetDragDropOp::New(PathNames));		}	}	return FReply::Unhandled();}bool SExtPathView::FolderAlreadyExists(const TSharedPtr< FTreeItem >& TreeItem, TSharedPtr< FTreeItem >& ExistingItem){	ExistingItem.Reset();	if ( TreeItem.IsValid() )	{		if ( TreeItem->Parent.IsValid() )		{			// This item has a parent, try to find it in its parent's children			TSharedPtr<FTreeItem> ParentItem = TreeItem->Parent.Pin();			for ( auto ChildIt = ParentItem->Children.CreateConstIterator(); ChildIt; ++ChildIt )			{				const TSharedPtr<FTreeItem>& Child = *ChildIt;				if ( Child != TreeItem && Child->FolderName == TreeItem->FolderName )				{					// The item is in its parent already					ExistingItem = Child;					break;				}			}		}		else		{			// This item is part of the root set			for ( auto RootIt = TreeRootItems.CreateConstIterator(); RootIt; ++RootIt )			{				const TSharedPtr<FTreeItem>& Root = *RootIt;				if ( Root != TreeItem && Root->FolderName == TreeItem->FolderName )				{					// The item is part of the root set already					ExistingItem = Root;					break;				}			}		}	}	return ExistingItem.IsValid();}void SExtPathView::RemoveFolderItem(const TSharedPtr< FTreeItem >& TreeItem){	if ( TreeItem.IsValid() )	{		if ( TreeItem->Parent.IsValid() )		{			// Remove this item from it's parent's list			TreeItem->Parent.Pin()->Children.Remove(TreeItem);		}		else		{			// This was a root node, remove from the root list			TreeRootItems.Remove(TreeItem);		}		TreeViewPtr->RequestTreeRefresh();	}}void SExtPathView::TreeAssetsOrPathsDropped(const TArray<FAssetData>& AssetList, const TArray<FString>& AssetPaths, const TSharedPtr<FTreeItem>& TreeItem){	DragDropHandler::HandleDropOnAssetFolder(		SharedThis(this), 		AssetList, 		AssetPaths, 		TreeItem->FolderPath, 		TreeItem->DisplayName, 		DragDropHandler::FExecuteCopyOrMove::CreateSP(this, &SExtPathView::ExecuteTreeDropCopy),		DragDropHandler::FExecuteCopyOrMove::CreateSP(this, &SExtPathView::ExecuteTreeDropMove),		DragDropHandler::FExecuteCopyOrMove::CreateSP(this, &SExtPathView::ExecuteTreeDropAdvancedCopy)		);}void SExtPathView::TreeFilesDropped(const TArray<FString>& FileNames, const TSharedPtr<FTreeItem>& TreeItem){	FAssetToolsModule& AssetToolsModule = FModuleManager::Get().LoadModuleChecked<FAssetToolsModule>("AssetTools");	AssetToolsModule.Get().ImportAssets( FileNames, TreeItem->FolderPath );}bool SExtPathView::IsTreeItemExpanded(TSharedPtr<FTreeItem> TreeItem) const{	return TreeViewPtr->IsItemExpanded(TreeItem);}bool SExtPathView::IsTreeItemSelected(TSharedPtr<FTreeItem> TreeItem) const{	return TreeViewPtr->IsItemSelected(TreeItem);}void SExtPathView::ExecuteTreeDropCopy(TArray<FAssetData> AssetList, TArray<FString> AssetPaths, FString DestinationPath){	if (AssetList.Num() > 0)	{		TArray<UObject*> DroppedObjects;		ExtContentBrowserUtils::GetObjectsInAssetData(AssetList, DroppedObjects);		ExtContentBrowserUtils::CopyAssets(DroppedObjects, DestinationPath);	}	if (AssetPaths.Num() > 0 && ExtContentBrowserUtils::CopyFolders(AssetPaths, DestinationPath))	{		TSharedPtr<FTreeItem> RootItem = FindItemRecursive(DestinationPath);		if (RootItem.IsValid())		{			TreeViewPtr->SetItemExpansion(RootItem, true);			// Select all the new folders			TreeViewPtr->ClearSelection();			for (const FString& AssetPath : AssetPaths)			{				const FString SubFolderName = FPackageName::GetLongPackageAssetName(AssetPath);				const FString NewPath = DestinationPath + TEXT("/") + SubFolderName;				TSharedPtr<FTreeItem> Item = FindItemRecursive(NewPath);				if (Item.IsValid())				{					TreeViewPtr->SetItemSelection(Item, true);					TreeViewPtr->RequestScrollIntoView(Item);				}			}		}	}}void SExtPathView::ExecuteTreeDropMove(TArray<FAssetData> AssetList, TArray<FString> AssetPaths, FString DestinationPath){#if ECB_LEGACY	if (AssetList.Num() > 0)	{		TArray<UObject*> DroppedObjects;		ContentBrowserUtils::GetObjectsInAssetData(AssetList, DroppedObjects);		ContentBrowserUtils::MoveAssets(DroppedObjects, DestinationPath);	}	// Prepare to fixup any asset paths that are favorites	TArray<FMovedContentFolder> MovedFolders;	for (const FString& OldPath : AssetPaths)	{		const FString SubFolderName = FPackageName::GetLongPackageAssetName(OldPath);		const FString NewPath = DestinationPath + TEXT("/") + SubFolderName;		MovedFolders.Add(FMovedContentFolder(OldPath, NewPath));	}	if (AssetPaths.Num() > 0 && ContentBrowserUtils::MoveFolders(AssetPaths, DestinationPath))	{		TSharedPtr<FTreeItem> RootItem = FindItemRecursive(DestinationPath);		if (RootItem.IsValid())		{			TreeViewPtr->SetItemExpansion(RootItem, true);			// Select all the new folders			TreeViewPtr->ClearSelection();			for (const FString& AssetPath : AssetPaths)			{				const FString SubFolderName = FPackageName::GetLongPackageAssetName(AssetPath);				const FString NewPath = DestinationPath + TEXT("/") + SubFolderName;				TSharedPtr<FTreeItem> Item = FindItemRecursive(NewPath);				if (Item.IsValid())				{					TreeViewPtr->SetItemSelection(Item, true);					TreeViewPtr->RequestScrollIntoView(Item);				}			}		}		OnFolderPathChanged.ExecuteIfBound(MovedFolders);	}#endif}void SExtPathView::GetPathItemList(const FString& InPath, const TArray<FString>& InRootPaths, TArray<FString>& OutPathItemList, bool bIncludeRootPath) const{	bool bFoundRootPath = false;	FString RootPath;	FString RelativePath;	for (int32 Index = 0; Index < InRootPaths.Num(); ++Index)	{		RootPath = InRootPaths[Index];		if (InPath.Find(*RootPath) == 0)		{			bFoundRootPath = true;			RelativePath = InPath.Mid(FCString::Strlen(*RootPath));			break;		}	}	RelativePath.ParseIntoArray(OutPathItemList, TEXT("/"), /*InCullEmpty=*/true);	if (bFoundRootPath && !RootPath.IsEmpty() && bIncludeRootPath)	{		OutPathItemList.Insert(RootPath, 0);	}}void SExtPathView::ExecuteTreeDropAdvancedCopy(TArray<FAssetData> AssetList, TArray<FString> AssetPaths, FString DestinationPath){	ExtContentBrowserUtils::BeginAdvancedCopyPackages(AssetList, AssetPaths, DestinationPath);}void SExtPathView::OnAssetRegistryPathAdded(const FString& Path){	// by sending the whole path we deliberately include any children	// of successful hits in the filtered list. 	if ( SearchBoxFolderFilter->PassesFilter( Path ) )	{		AddPath(Path);	}}void SExtPathView::OnAssetRegistryPathRemoved(const FString& Path){	// by sending the whole path we deliberately include any children	// of successful hits in the filtered list. 	if ( SearchBoxFolderFilter->PassesFilter( Path ) )	{		RemovePath(Path);	}}void SExtPathView::OnAssetRegistryRootPathAdded(const FString& Path){	Populate();}void SExtPathView::OnAssetRegistryRootPathRemoved(const FString& Path){	Populate();	ClearSelection();}void SExtPathView::OnAssetRegistryRootPathUpdated(){	Populate();}void SExtPathView::OnAssetRegistryFolderStartGathering(const TArray<FString>& Paths){	//Populate();	//ClearSelection();}void SExtPathView::OnAssetRegistryFolderFinishGathering(const FString& InPath, const FString& InRootPath){	//Populate();	if (SearchBoxFolderFilter->PassesFilter(InPath))	{		AddPath(InPath, &InRootPath);		TArray<FString> SelectedPath = GetSelectedPaths();		if (SelectedPath.Contains(InPath) && OnPathSelected.IsBound())		{			// Refresh			OnPathSelected.Execute(InPath);		}	}}void SExtPathView::OnAssetRegistrySearchCompleted(){	// If there were any more initial paths, they no longer exist so clear them now.	PendingInitialPaths.Empty();}void SExtPathView::OnFolderPopulated(const FString& Path){	OnAssetRegistryPathAdded(Path);}void SExtPathView::OnContentPathMountedOrDismounted( const FString& AssetPath, const FString& FilesystemPath ){	/**	 * Hotfix	 * For some reason this widget sometime outlive the slate application shutdown	 * Validating that Slate application base is initialized will at least avoid the possible crash	 */	if ( FSlateApplicationBase::IsInitialized() )	{		// A new content path has appeared, so we should refresh out root set of paths		RegisterActiveTimer(0.f, FWidgetActiveTimerDelegate::CreateSP(this, &SExtPathView::TriggerRepopulate));	}}void SExtPathView::OnClassHierarchyUpdated(){	// The class hierarchy has changed in some way, so we need to refresh our set of paths	RegisterActiveTimer(0.f, FWidgetActiveTimerDelegate::CreateSP(this, &SExtPathView::TriggerRepopulate));}void SExtPathView::HandleSettingChanged(FName PropertyName){#if ECB_TODO	if ((PropertyName == GET_MEMBER_NAME_CHECKED(UExtContentBrowserSettings, DisplayEmptyFolders)) ||		(PropertyName == "DisplayDevelopersFolder") ||		(PropertyName == "DisplayEngineFolder") ||		(PropertyName == "DisplayPluginFolders") ||		(PropertyName == "DisplayL10NFolder") ||		(PropertyName == NAME_None))	// @todo: Needed if PostEditChange was called manually, for now	{		TSharedRef<FEmptyFolderVisibilityManager> EmptyFolderVisibilityManager = FContentBrowserSingleton::Get().GetEmptyFolderVisibilityManager();		// If the dev or engine folder is no longer visible but we're inside it...		const bool bDisplayEmpty = GetDefault<UExtContentBrowserSettings>()->DisplayEmptyFolders;		const bool bDisplayDev = GetDefault<UExtContentBrowserSettings>()->GetDisplayDevelopersFolder();		const bool bDisplayEngine = GetDefault<UExtContentBrowserSettings>()->GetDisplayEngineFolder();		const bool bDisplayPlugins = GetDefault<UExtContentBrowserSettings>()->GetDisplayPluginFolders();		const bool bDisplayL10N = GetDefault<UExtContentBrowserSettings>()->GetDisplayL10NFolder();		if (!bDisplayEmpty || !bDisplayDev || !bDisplayEngine || !bDisplayPlugins || !bDisplayL10N)		{			const FString OldSelectedPath = GetSelectedPath();			const ContentBrowserUtils::ECBFolderCategory OldFolderCategory = ContentBrowserUtils::GetFolderCategory(OldSelectedPath);			if ((!bDisplayEmpty && !EmptyFolderVisibilityManager->ShouldShowPath(OldSelectedPath)) || 				(!bDisplayDev && OldFolderCategory == ContentBrowserUtils::ECBFolderCategory::DeveloperContent) || 				(!bDisplayEngine && (OldFolderCategory == ContentBrowserUtils::ECBFolderCategory::EngineContent || OldFolderCategory == ContentBrowserUtils::ECBFolderCategory::EngineClasses)) || 				(!bDisplayPlugins && (OldFolderCategory == ContentBrowserUtils::ECBFolderCategory::PluginContent || OldFolderCategory == ContentBrowserUtils::ECBFolderCategory::PluginClasses)) ||				(!bDisplayL10N && ContentBrowserUtils::IsLocalizationFolder(OldSelectedPath)))			{				// Set the folder back to the root, and refresh the contents				TSharedPtr<FTreeItem> GameRoot = FindItemRecursive(TEXT("/Game"));				if ( GameRoot.IsValid() )				{					TreeViewPtr->SetSelection(GameRoot);				}				else				{					TreeViewPtr->ClearSelection();				}			}		}		// Update our path view so that it can include/exclude the dev folder		Populate();		// If the dev or engine folder has become visible and we're inside it...		if (bDisplayDev || bDisplayEngine || bDisplayPlugins || bDisplayL10N)		{			const FString NewSelectedPath = GetSelectedPath();			const ContentBrowserUtils::ECBFolderCategory NewFolderCategory = ContentBrowserUtils::GetFolderCategory(NewSelectedPath);			if ((bDisplayDev && NewFolderCategory == ContentBrowserUtils::ECBFolderCategory::DeveloperContent) || 				(bDisplayEngine && (NewFolderCategory == ContentBrowserUtils::ECBFolderCategory::EngineContent || NewFolderCategory == ContentBrowserUtils::ECBFolderCategory::EngineClasses)) || 				(bDisplayPlugins && (NewFolderCategory == ContentBrowserUtils::ECBFolderCategory::PluginContent || NewFolderCategory == ContentBrowserUtils::ECBFolderCategory::PluginClasses)) ||				(bDisplayL10N && ContentBrowserUtils::IsLocalizationFolder(NewSelectedPath)))			{				// Refresh the contents				OnPathSelected.ExecuteIfBound(NewSelectedPath);			}		}	}#endif}void SFavoritePathView::Construct(const FArguments& InArgs){	SAssignNew(TreeViewPtr, STreeView< TSharedPtr<FTreeItem> >)		.TreeItemsSource(&TreeRootItems)		.OnGetChildren(this, &SFavoritePathView::GetChildrenForTree)		.OnGenerateRow(this, &SFavoritePathView::GenerateTreeRow)		.OnItemScrolledIntoView(this, &SFavoritePathView::TreeItemScrolledIntoView)		.ItemHeight(18)		.SelectionMode(InArgs._SelectionMode)		.OnSelectionChanged(this, &SFavoritePathView::TreeSelectionChanged)		.OnContextMenuOpening(this, &SFavoritePathView::MakePathViewContextMenu)		.ClearSelectionOnClick(false);	SExtPathView::Construct(InArgs);}void SFavoritePathView::Populate(){	// Don't allow the selection changed delegate to be fired here	FScopedPreventTreeItemChangedDelegate DelegatePrevention(SharedThis(this));	// Clear all root items and clear selection	TreeRootItems.Empty();	TreeViewPtr->ClearSelection();	const TArray<FString> FavoritePaths = ExtContentBrowserUtils::GetFavoriteFolders();	// we have a text filter, expand all parents of matching folders	for (const FString& Path : FavoritePaths)	{		// by sending the whole path we deliberately include any children		// of successful hits in the filtered list. 		if (SearchBoxFolderFilter->PassesFilter(Path))		{			TSharedPtr<FTreeItem> Item = AddPath(Path);			if (Item.IsValid())			{				const bool bSelectedItem = LastSelectedPaths.Contains(Item->FolderPath);				if (bSelectedItem)				{					// Tree items that match the last broadcasted paths should be re-selected them after they are added					TreeViewPtr->SetItemSelection(Item, true);					TreeViewPtr->RequestScrollIntoView(Item);				}			}		}	}	SortRootItems();}void SFavoritePathView::SaveSettings(const FString& IniFilename, const FString& IniSection, const FString& SettingsString) const{	SExtPathView::SaveSettings(IniFilename, IniSection, SettingsString);	FString FavoritePathsString;	const TArray<FString> FavoritePaths = ExtContentBrowserUtils::GetFavoriteFolders();	for (const FString& PathIt : FavoritePaths)	{		if (FavoritePathsString.Len() > 0)		{			FavoritePathsString += TEXT(",");		}		FavoritePathsString += PathIt;	}	GConfig->SetString(*IniSection, TEXT("FavoritePaths"), *FavoritePathsString, IniFilename);}void SFavoritePathView::LoadSettings(const FString& IniFilename, const FString& IniSection, const FString& SettingsString){	SExtPathView::LoadSettings(IniFilename, IniSection, SettingsString);	// Selected Paths	FString SelectedPathsString;	TArray<FString> NewFavoritePaths;	if (GConfig->GetString(*IniSection, TEXT("FavoritePaths"), SelectedPathsString, IniFilename))	{		SelectedPathsString.ParseIntoArray(NewFavoritePaths, TEXT(","), /*bCullEmpty*/true);	}	if (NewFavoritePaths.Num() > 0)	{		// Keep track if we changed at least one source so we know to fire the bulk selection changed delegate later		bool bAddedAtLeastOnePath = false;		{			// If the selected paths is empty, the path was "All assets"			// This should handle that case properly			for (int32 PathIdx = 0; PathIdx < NewFavoritePaths.Num(); ++PathIdx)			{				const FString& Path = NewFavoritePaths[PathIdx];				ExtContentBrowserUtils::AddFavoriteFolder(Path, false);				bAddedAtLeastOnePath = true;			}		}		if (bAddedAtLeastOnePath)		{			Populate();		}	}}void SFavoritePathView::OnAssetTreeSearchBoxChanged(const FText& InSearchText){	SExtPathView::OnAssetTreeSearchBoxChanged(InSearchText);	OnFavoriteSearchChanged.ExecuteIfBound(InSearchText);}void SFavoritePathView::OnAssetTreeSearchBoxCommitted(const FText& InSearchText, ETextCommit::Type InCommitType){	SExtPathView::OnAssetTreeSearchBoxCommitted(InSearchText, InCommitType);	OnFavoriteSearchCommitted.ExecuteIfBound(InSearchText, InCommitType);}void SFavoritePathView::SetSelectedPaths(const TArray<FString>& Paths){	if (!ensure(TreeViewPtr.IsValid()))	{		return;	}	if (!SearchBoxPtr->GetText().IsEmpty())	{		// Clear the search box so the selected paths will be visible		SearchBoxPtr->SetText(FText::GetEmpty());	}	// Prevent the selection changed delegate since the invoking code requested it	FScopedPreventTreeItemChangedDelegate DelegatePrevention(SharedThis(this));	// If the selection was changed before all pending initial paths were found, stop attempting to select them	PendingInitialPaths.Empty();	// Clear the selection to start, then add the selected paths as they are found	TreeViewPtr->ClearSelection();	for (int32 PathIdx = 0; PathIdx < Paths.Num(); ++PathIdx)	{		const FString& Path = Paths[PathIdx];		TArray<FString> PathItemList;		Path.ParseIntoArray(PathItemList, TEXT("/"), /*InCullEmpty=*/true);		if (PathItemList.Num())		{			// There is at least one element in the path			TArray<TSharedPtr<FTreeItem>> TreeItems;			// Find the first item in the root items list			for (int32 RootItemIdx = 0; RootItemIdx < TreeRootItems.Num(); ++RootItemIdx)			{				if (TreeRootItems[RootItemIdx]->FolderName == PathItemList[0])				{					// Found the first item in the path					TreeItems.Add(TreeRootItems[RootItemIdx]);					break;				}			}			// If found in the root items list, try to find the childmost item matching the path			if (TreeItems.Num() > 0)			{				for (int32 PathItemIdx = 1; PathItemIdx < PathItemList.Num(); ++PathItemIdx)				{					const FString& PathItemName = PathItemList[PathItemIdx];					const TSharedPtr<FTreeItem> ChildItem = TreeItems.Last()->GetChild(PathItemName);					if (ChildItem.IsValid())					{						// Update tree items list						TreeItems.Add(ChildItem);					}					else					{						// Could not find the child item						break;					}				}				// Set the selection to the closest found folder and scroll it into view				TreeViewPtr->SetItemSelection(TreeItems.Last(), true);				TreeViewPtr->RequestScrollIntoView(TreeItems.Last());			}			else			{				// Could not even find the root path... skip			}		}		else		{			// No path items... skip		}	}}TSharedPtr<FTreeItem> SFavoritePathView::AddPath(const FString& InPath, const FString* RootPathPtr/* = nullptr*/, bool bUserNamed /*= false*/){	if (!ensure(TreeViewPtr.IsValid()))	{		// No tree view for some reason		return TSharedPtr<FTreeItem>();	}	FString Path = InPath;	FString RootPath;	if (RootPathPtr != nullptr)	{		RootPath = *RootPathPtr;		if (Path.Find(*RootPath) == 0)		{			Path = Path.Mid(FCString::Strlen(*RootPath));		}	}	TArray<FString> PathItemList;	Path.ParseIntoArray(PathItemList, TEXT("/"), /*InCullEmpty=*/true);	if (PathItemList.Num())	{		// There is at least one element in the path		const FString FolderName = PathItemList.Last();		const FString FolderPath = Path;		// Make sure the item is not already in the list		for (int32 RootItemIdx = 0; RootItemIdx < TreeRootItems.Num(); ++RootItemIdx)		{			if (TreeRootItems[RootItemIdx]->FolderPath == FolderPath)			{				// The root to add was already in the list return it here				return TreeRootItems[RootItemIdx];			}		}		TSharedPtr<struct FTreeItem> NewItem = nullptr;		// If this isn't an engine folder or we want to show them, add		const bool bDisplayEngine = GetDefault<UExtContentBrowserSettings>()->GetDisplayEngineFolder();		const bool bDisplayPlugins = GetDefault<UExtContentBrowserSettings>()->GetDisplayPluginFolders();		const bool bDisplayCpp = GetDefault<UExtContentBrowserSettings>()->GetDisplayCppFolders();		const bool bDisplayLoc = GetDefault<UExtContentBrowserSettings>()->GetDisplayL10NFolder();		// Filter out classes folders if we're not showing them.		if (!bDisplayCpp && ExtContentBrowserUtils::IsClassesFolder(FolderName))		{			return nullptr;		}		// Filter out plugin folders		bool bIsPlugin = false;		EPluginLoadedFrom PluginSource = EPluginLoadedFrom::Engine; // init to avoid warning		if (!bDisplayEngine || !bDisplayPlugins)		{			bIsPlugin = ExtContentBrowserUtils::IsPluginFolder(FolderName, &PluginSource);		}		if ((bDisplayEngine || !ExtContentBrowserUtils::IsEngineFolder(FolderName)) &&			((bDisplayEngine && bDisplayPlugins) || !(bIsPlugin && PluginSource == EPluginLoadedFrom::Engine)) &&			(bDisplayPlugins || !(bIsPlugin && PluginSource == EPluginLoadedFrom::Project)) &&			(bDisplayLoc || !ExtContentBrowserUtils::IsLocalizationFolder(FolderName)))		{			const FText DisplayName = ExtContentBrowserUtils::GetRootDirDisplayName(FolderName);			NewItem = MakeShareable(new FTreeItem(FText::FromString(FolderName), FolderName, FolderPath, RootPath, TSharedPtr<FTreeItem>()));			TreeRootItems.Add(NewItem);			TreeViewPtr->RequestTreeRefresh();			TreeViewPtr->SetSelection(NewItem);		}		return NewItem;	}	return TSharedPtr<FTreeItem>();}TSharedRef<ITableRow> SFavoritePathView::GenerateTreeRow(TSharedPtr<FTreeItem> TreeItem, const TSharedRef<STableViewBase>& OwnerTable){	check(TreeItem.IsValid());	return		SNew( STableRow< TSharedPtr<FTreeItem> >, OwnerTable )		.OnDragDetected( this, &SFavoritePathView::OnFolderDragDetected )		[			SNew(SExtAssetTreeItem)			.TreeItem(TreeItem)			.OnAssetsOrPathsDragDropped(this, &SFavoritePathView::TreeAssetsOrPathsDropped)			.OnFilesDragDropped(this, &SFavoritePathView::TreeFilesDropped)			.IsItemExpanded(false)			.HighlightText(this, &SFavoritePathView::GetHighlightText)			.IsSelected(this, &SFavoritePathView::IsTreeItemSelected, TreeItem)			.FontOverride(FAppStyle::GetFontStyle("ContentBrowser.SourceTreeItemFont"))		];}void SFavoritePathView::OnAssetRegistryPathAdded(const FString& Path){}void SFavoritePathView::OnAssetRegistryPathRemoved(const FString& Path){	ExtContentBrowserUtils::RemoveFavoriteFolder(Path);	Populate();}void SFavoritePathView::ExecuteTreeDropMove(TArray<FAssetData> AssetList, TArray<FString> AssetPaths, FString DestinationPath){#if ECB_LEGACY	if (AssetList.Num() > 0)	{		TArray<UObject*> DroppedObjects;		ContentBrowserUtils::GetObjectsInAssetData(AssetList, DroppedObjects);		ContentBrowserUtils::MoveAssets(DroppedObjects, DestinationPath);	}	// Prepare to fixup any asset paths that are favorites	TArray<FMovedContentFolder> MovedFolders;	for (const FString& OldPath : AssetPaths)	{		const FString SubFolderName = FPackageName::GetLongPackageAssetName(OldPath);		const FString NewPath = DestinationPath + TEXT("/") + SubFolderName;		MovedFolders.Add(FMovedContentFolder(OldPath, NewPath));	}	FixupFavoritesFromExternalChange(MovedFolders);	ContentBrowserUtils::MoveFolders(AssetPaths, DestinationPath);#endif}FText SExtPathView::GetTreeTitle() const{	const UExtContentBrowserSettings* ExtContentBrowserSettings = GetDefault<UExtContentBrowserSettings>();	const bool bCacheMode = ExtContentBrowserSettings->bCacheMode;	if (bCacheMode)	{		const FString& CacheFilePath = ExtContentBrowserSettings->CacheFilePath.FilePath;		const FString FileName = FPaths::GetBaseFilename(CacheFilePath);		return FText::Format(LOCTEXT("CacheModeTreeTitle", "{0}"), FText::FromString(FileName));	}	else	{		return TreeTitle;	}}EVisibility SExtPathView::GetTreeTitleVisibility() const{		const UExtContentBrowserSettings* ExtContentBrowserSettings = GetDefault<UExtContentBrowserSettings>();	const bool bCacheMode = ExtContentBrowserSettings->bCacheMode;	if (bCacheMode)	{		return EVisibility::Visible;	}	else	{		return EVisibility::Collapsed;	}}#undef LOCTEXT_NAMESPACE
 |