SExtPathView.cpp 62 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034
  1. // Copyright 2017-2021 marynate. All Rights Reserved.
  2. #include "SExtPathView.h"
  3. #include "ExtContentBrowser.h"
  4. #include "ExtContentBrowserSingleton.h"
  5. #include "ExtContentBrowserUtils.h"
  6. #include "ExtPackageUtils.h"
  7. #include "ExtPathViewTypes.h"
  8. #include "ExtSourcesViewWidgets.h"
  9. #include "DragDropHandler.h"
  10. #include "Adapters/SourcesData.h"
  11. #include "Layout/WidgetPath.h"
  12. #include "Application/SlateApplicationBase.h"
  13. #include "Framework/Application/SlateApplication.h"
  14. #include "Widgets/Input/SSearchBox.h"
  15. #include "Widgets/Layout/SSeparator.h"
  16. #include "EditorStyleSet.h"
  17. #include "AssetRegistry/AssetRegistryModule.h"
  18. #include "IAssetTools.h"
  19. #include "AssetToolsModule.h"
  20. #include "DragAndDrop/AssetDragDropOp.h"
  21. #include "HAL/FileManager.h"
  22. #include "Misc/ConfigCacheIni.h"
  23. #if ECB_WIP_HISTORY
  24. #include "HistoryManager.h"
  25. #endif
  26. #define LOCTEXT_NAMESPACE "ExtContentBrowser"
  27. SExtPathView::~SExtPathView()
  28. {
  29. #if ECB_LEGACY
  30. // Unsubscribe from content path events
  31. FPackageName::OnContentPathMounted().RemoveAll( this );
  32. FPackageName::OnContentPathDismounted().RemoveAll( this );
  33. // Unsubscribe from class events
  34. if ( bAllowClassesFolder )
  35. {
  36. TSharedRef<FNativeClassHierarchy> NativeClassHierarchy = FContentBrowserSingleton::Get().GetNativeClassHierarchy();
  37. NativeClassHierarchy->OnClassHierarchyUpdated().RemoveAll( this );
  38. }
  39. // Unsubscribe from folder population events
  40. {
  41. TSharedRef<FEmptyFolderVisibilityManager> EmptyFolderVisibilityManager = FContentBrowserSingleton::Get().GetEmptyFolderVisibilityManager();
  42. EmptyFolderVisibilityManager->OnFolderPopulated().RemoveAll(this);
  43. }
  44. // Load the asset registry module to stop listening for updates
  45. FAssetRegistryModule* AssetRegistryModule = FModuleManager::GetModulePtr<FAssetRegistryModule>(TEXT("AssetRegistry"));
  46. if(AssetRegistryModule)
  47. {
  48. AssetRegistryModule->Get().OnPathAdded().RemoveAll(this);
  49. AssetRegistryModule->Get().OnPathRemoved().RemoveAll(this);
  50. AssetRegistryModule->Get().OnFilesLoaded().RemoveAll(this);
  51. }
  52. #endif
  53. FExtContentBrowserSingleton::GetAssetRegistry().OnRootPathAdded().RemoveAll(this);
  54. FExtContentBrowserSingleton::GetAssetRegistry().OnRootPathRemoved().RemoveAll(this);
  55. FExtContentBrowserSingleton::GetAssetRegistry().OnRootPathUpdated().RemoveAll(this);
  56. FExtContentBrowserSingleton::GetAssetRegistry().OnFolderStartGathering().RemoveAll(this);
  57. FExtContentBrowserSingleton::GetAssetRegistry().OnFolderFinishGathering().RemoveAll(this);
  58. SearchBoxFolderFilter->OnChanged().RemoveAll( this );
  59. }
  60. void SExtPathView::Construct( const FArguments& InArgs )
  61. {
  62. OnPathSelected = InArgs._OnPathSelected;
  63. bAllowContextMenu = InArgs._AllowContextMenu;
  64. OnGetFolderContextMenu = InArgs._OnGetFolderContextMenu;
  65. OnGetPathContextMenuExtender = InArgs._OnGetPathContextMenuExtender;
  66. bAllowClassesFolder = InArgs._AllowClassesFolder;
  67. PreventTreeItemChangedDelegateCount = 0;
  68. TreeTitle = LOCTEXT("AssetTreeTitle", "Asset Tree");
  69. PluginVersionText = FExtContentBrowserSingleton::GetPluginVersionText();
  70. if ( InArgs._FocusSearchBoxWhenOpened )
  71. {
  72. RegisterActiveTimer( 0.f, FWidgetActiveTimerDelegate::CreateSP( this, &SExtPathView::SetFocusPostConstruct ) );
  73. }
  74. // Listen for when view settings are changed
  75. UExtContentBrowserSettings::OnSettingChanged().AddSP(this, &SExtPathView::HandleSettingChanged);
  76. //Setup the SearchBox filter
  77. SearchBoxFolderFilter = MakeShareable( new FolderTextFilter( FolderTextFilter::FItemToStringArray::CreateSP( this, &SExtPathView::PopulateFolderSearchStrings ) ) );
  78. SearchBoxFolderFilter->OnChanged().AddSP( this, &SExtPathView::FilterUpdated );
  79. // Listen to find out when new game content paths are mounted or dismounted, so that we can refresh our root set of paths
  80. FPackageName::OnContentPathMounted().AddSP( this, &SExtPathView::OnContentPathMountedOrDismounted );
  81. FPackageName::OnContentPathDismounted().AddSP( this, &SExtPathView::OnContentPathMountedOrDismounted );
  82. #if ECB_LEGACY
  83. // Listen to find out when the available classes are changed, so that we can refresh our paths
  84. if ( bAllowClassesFolder )
  85. {
  86. TSharedRef<FNativeClassHierarchy> NativeClassHierarchy = FContentBrowserSingleton::Get().GetNativeClassHierarchy();
  87. NativeClassHierarchy->OnClassHierarchyUpdated().AddSP( this, &SExtPathView::OnClassHierarchyUpdated );
  88. }
  89. // Listen to find out when previously empty paths are populated with content
  90. {
  91. TSharedRef<FEmptyFolderVisibilityManager> EmptyFolderVisibilityManager = FContentBrowserSingleton::Get().GetEmptyFolderVisibilityManager();
  92. EmptyFolderVisibilityManager->OnFolderPopulated().AddSP(this, &SExtPathView::OnFolderPopulated);
  93. }
  94. #endif
  95. if (!TreeViewPtr.IsValid())
  96. {
  97. SAssignNew(TreeViewPtr, STreeView< TSharedPtr<FTreeItem> >)
  98. .TreeItemsSource(&TreeRootItems)
  99. .OnGenerateRow(this, &SExtPathView::GenerateTreeRow)
  100. .OnItemScrolledIntoView(this, &SExtPathView::TreeItemScrolledIntoView)
  101. .ItemHeight(18)
  102. .SelectionMode(InArgs._SelectionMode)
  103. .OnSelectionChanged(this, &SExtPathView::TreeSelectionChanged)
  104. .OnExpansionChanged(this, &SExtPathView::TreeExpansionChanged)
  105. .OnGetChildren(this, &SExtPathView::GetChildrenForTree)
  106. .OnSetExpansionRecursive(this, &SExtPathView::SetTreeItemExpansionRecursive)
  107. .OnContextMenuOpening(this, &SExtPathView::MakePathViewContextMenu)
  108. .ClearSelectionOnClick(false)
  109. .HighlightParentNodesForSelection(true);
  110. }
  111. ChildSlot
  112. [
  113. SNew(SVerticalBox)
  114. // Search >>
  115. #if ECB_FOLD
  116. + SVerticalBox::Slot()
  117. .AutoHeight()
  118. .Padding(0, 1, 0, 3)
  119. [
  120. SNew(SHorizontalBox)
  121. + SHorizontalBox::Slot()
  122. .AutoWidth()
  123. [
  124. InArgs._SearchContent.Widget
  125. ]
  126. + SHorizontalBox::Slot()
  127. .FillWidth(1.0f)
  128. [
  129. SAssignNew(SearchBoxPtr, SSearchBox)
  130. .Visibility(InArgs._SearchBarVisibility)
  131. .HintText( LOCTEXT( "AssetTreeSearchBoxHint", "Search Folders" ) )
  132. .OnTextChanged( this, &SExtPathView::OnAssetTreeSearchBoxChanged )
  133. .OnTextCommitted( this, &SExtPathView::OnAssetTreeSearchBoxCommitted )
  134. ]
  135. ]
  136. #endif // Search <<
  137. // Tree Section >>
  138. #if ECB_FOLD
  139. #if 0
  140. // Tree title
  141. + SVerticalBox::Slot()
  142. .AutoHeight()
  143. .Padding(0)
  144. [
  145. SNew(SBorder)
  146. .BorderBackgroundColor(FLinearColor(1.f, 1.f, 1.f, 1.f))
  147. .BorderImage(FAppStyle::GetBrush("ToolPanel.GroupBorder"))
  148. .Padding(1.f)
  149. .Visibility(this, &SExtPathView::GetTreeTitleVisibility)
  150. ]
  151. +SVerticalBox::Slot()
  152. .AutoHeight()
  153. .Padding(2)
  154. [
  155. SNew(SBorder)
  156. .BorderBackgroundColor(FLinearColor(0.f, 0.f, 0.f, 0.1f))
  157. .BorderImage(FAppStyle::GetBrush("ToolPanel.GroupBorder"))
  158. .Padding(4)
  159. .Visibility(this, &SExtPathView::GetTreeTitleVisibility)
  160. [
  161. SNew(SHorizontalBox)
  162. +SHorizontalBox::Slot()
  163. .Padding(4, 0, 0, 0)
  164. [
  165. SNew(STextBlock)
  166. .Font( FAppStyle::GetFontStyle("ContentBrowser.SourceTitleFont") )
  167. //.TextStyle(FAppStyle::Get(), "ContentBrowser.TopBar.Font")
  168. .Text(this, &SExtPathView::GetTreeTitle)
  169. //.Visibility(InArgs._ShowTreeTitle ? EVisibility::Visible : EVisibility::Collapsed)
  170. .Visibility(this, &SExtPathView::GetTreeTitleVisibility)
  171. ]
  172. ]
  173. ]
  174. + SVerticalBox::Slot()
  175. .AutoHeight()
  176. .Padding(0)
  177. [
  178. SNew(SBorder)
  179. .BorderBackgroundColor(FLinearColor(1.f, 1.f, 1.f, 1.f))
  180. .BorderImage(FAppStyle::GetBrush("ToolPanel.GroupBorder"))
  181. .Padding(1.f)
  182. .Visibility(this, &SExtPathView::GetTreeTitleVisibility)
  183. ]
  184. #endif
  185. // Separator
  186. +SVerticalBox::Slot()
  187. .AutoHeight()
  188. .Padding(0, 0, 0, 2)
  189. [
  190. SNew(SSeparator)
  191. .Visibility( ( InArgs._ShowSeparator) ? EVisibility::Visible : EVisibility::Collapsed )
  192. ]
  193. // Tree
  194. +SVerticalBox::Slot()
  195. .FillHeight(1.f)
  196. [
  197. TreeViewPtr.ToSharedRef()
  198. ]
  199. // Tree title
  200. + SVerticalBox::Slot()
  201. .AutoHeight()
  202. .Padding(0, 2, 0, 0)
  203. [
  204. SNew(SBorder)
  205. .BorderBackgroundColor(FLinearColor(1.f, 1.f, 1.f, 1.f))
  206. .BorderImage(FAppStyle::GetBrush("ToolPanel.GroupBorder"))
  207. .Padding(1.f)
  208. .Visibility(this, &SExtPathView::GetTreeTitleVisibility)
  209. ]
  210. + SVerticalBox::Slot()
  211. .AutoHeight()
  212. .Padding(2)
  213. [
  214. SNew(SBorder)
  215. .BorderBackgroundColor(FLinearColor(0.f, 0.f, 0.f, 0.1f))
  216. .BorderImage(FAppStyle::GetBrush("ToolPanel.GroupBorder"))
  217. .Padding(2)
  218. .Visibility(this, &SExtPathView::GetTreeTitleVisibility)
  219. [
  220. SNew(SHorizontalBox)
  221. + SHorizontalBox::Slot()
  222. .HAlign(HAlign_Center)
  223. .Padding(4, 0, 0, 0)
  224. [
  225. SNew(STextBlock)
  226. .Font(FAppStyle::GetFontStyle("ContentBrowser.SourceTitleFont"))
  227. .ColorAndOpacity(FLinearColor(1, 1, 1, 0.4f))
  228. .Text(this, &SExtPathView::GetTreeTitle)
  229. .Visibility(this, &SExtPathView::GetTreeTitleVisibility)
  230. ]
  231. ]
  232. ]
  233. + SVerticalBox::Slot()
  234. .AutoHeight()
  235. .Padding(0)
  236. [
  237. SNew(SBorder)
  238. .BorderBackgroundColor(FLinearColor(1.f, 1.f, 1.f, 1.f))
  239. .BorderImage(FAppStyle::GetBrush("ToolPanel.GroupBorder"))
  240. .Padding(1.f)
  241. .Visibility(this, &SExtPathView::GetTreeTitleVisibility)
  242. ]
  243. #endif // Tree Section <<
  244. #if ECB_TODO
  245. // Version
  246. + SVerticalBox::Slot()
  247. .AutoHeight()
  248. .Padding(2, 2, 0, 1)
  249. .HAlign(HAlign_Left)
  250. [
  251. SNew (SHorizontalBox)
  252. +SHorizontalBox::Slot().AutoWidth()
  253. [
  254. SNew(STextBlock).Text(this, &SExtPathView::GetPluginVersionText)
  255. .ColorAndOpacity(FLinearColor::Gray)
  256. ]
  257. ]
  258. #endif
  259. ];
  260. #if ECB_LEGACY
  261. // Load the asset registry module to listen for updates
  262. FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked<FAssetRegistryModule>(TEXT("AssetRegistry"));
  263. AssetRegistryModule.Get().OnPathAdded().AddSP( this, &SExtPathView::OnAssetRegistryPathAdded );
  264. AssetRegistryModule.Get().OnPathRemoved().AddSP( this, &SExtPathView::OnAssetRegistryPathRemoved );
  265. AssetRegistryModule.Get().OnFilesLoaded().AddSP( this, &SExtPathView::OnAssetRegistrySearchCompleted );
  266. #endif
  267. FExtContentBrowserSingleton::GetAssetRegistry().OnRootPathAdded().AddSP(this, &SExtPathView::OnAssetRegistryRootPathAdded);
  268. FExtContentBrowserSingleton::GetAssetRegistry().OnRootPathRemoved().AddSP(this, &SExtPathView::OnAssetRegistryRootPathRemoved);
  269. FExtContentBrowserSingleton::GetAssetRegistry().OnRootPathUpdated().AddSP(this, &SExtPathView::OnAssetRegistryRootPathUpdated);
  270. FExtContentBrowserSingleton::GetAssetRegistry().OnFolderStartGathering().AddSP(this, &SExtPathView::OnAssetRegistryFolderStartGathering);
  271. FExtContentBrowserSingleton::GetAssetRegistry().OnFolderFinishGathering().AddSP(this, &SExtPathView::OnAssetRegistryFolderFinishGathering);
  272. // Add all paths currently gathered from the asset registry
  273. Populate();
  274. // Always expand the game root initially
  275. static const FString GameRootName = TEXT("Game");
  276. for ( auto RootIt = TreeRootItems.CreateConstIterator(); RootIt; ++RootIt )
  277. {
  278. if ( (*RootIt)->FolderName == GameRootName )
  279. {
  280. TreeViewPtr->SetItemExpansion(*RootIt, true);
  281. }
  282. }
  283. }
  284. void SExtPathView::Tick(const FGeometry& AllottedGeometry, const double InCurrentTime, const float InDeltaTime)
  285. {
  286. #if ECB_FEA_ASYNC_FOLDER_DISCOVERY
  287. if (FExtContentBrowserSingleton::GetAssetRegistry().GetAndTrimFolderGatherResult())
  288. {
  289. Populate();
  290. }
  291. #endif
  292. }
  293. void SExtPathView::SetSelectedPaths(const TArray<FString>& Paths)
  294. {
  295. if ( !ensure(TreeViewPtr.IsValid()) )
  296. {
  297. return;
  298. }
  299. if ( !SearchBoxPtr->GetText().IsEmpty() )
  300. {
  301. // Clear the search box so the selected paths will be visible
  302. SearchBoxPtr->SetText( FText::GetEmpty() );
  303. }
  304. // Prevent the selection changed delegate since the invoking code requested it
  305. FScopedPreventTreeItemChangedDelegate DelegatePrevention( SharedThis(this) );
  306. // If the selection was changed before all pending initial paths were found, stop attempting to select them
  307. PendingInitialPaths.Empty();
  308. // Clear the selection to start, then add the selected paths as they are found
  309. TreeViewPtr->ClearSelection();
  310. TArray<FString> RootContentPaths;
  311. FExtContentBrowserSingleton::GetAssetRegistry().QueryRootContentPaths(RootContentPaths);
  312. for (int32 PathIdx = 0; PathIdx < Paths.Num(); ++PathIdx)
  313. {
  314. const FString& Path = Paths[PathIdx];
  315. TArray<FString> PathItemList;
  316. GetPathItemList(Path, RootContentPaths, PathItemList, /*bIncludeRootPath*/true);
  317. if ( PathItemList.Num() )
  318. {
  319. // There is at least one element in the path
  320. TArray<TSharedPtr<FTreeItem>> TreeItems;
  321. // Find the first item in the root items list
  322. for ( int32 RootItemIdx = 0; RootItemIdx < TreeRootItems.Num(); ++RootItemIdx )
  323. {
  324. if ( TreeRootItems[RootItemIdx]->FolderPath == PathItemList[0] )
  325. {
  326. // Found the first item in the path
  327. TreeItems.Add(TreeRootItems[RootItemIdx]);
  328. break;
  329. }
  330. }
  331. // If found in the root items list, try to find the childmost item matching the path
  332. if ( TreeItems.Num() > 0 )
  333. {
  334. for ( int32 PathItemIdx = 1; PathItemIdx < PathItemList.Num(); ++PathItemIdx )
  335. {
  336. const FString& PathItemName = PathItemList[PathItemIdx];
  337. const TSharedPtr<FTreeItem> ChildItem = TreeItems.Last()->GetChild(PathItemName);
  338. if ( ChildItem.IsValid() )
  339. {
  340. // Update tree items list
  341. TreeItems.Add(ChildItem);
  342. }
  343. else
  344. {
  345. // Could not find the child item
  346. break;
  347. }
  348. }
  349. // Expand all the tree folders up to but not including the last one.
  350. for (int32 ItemIdx = 0; ItemIdx < TreeItems.Num() - 1; ++ItemIdx)
  351. {
  352. TreeViewPtr->SetItemExpansion(TreeItems[ItemIdx], true);
  353. }
  354. // Set the selection to the closest found folder and scroll it into view
  355. TreeViewPtr->SetItemSelection(TreeItems.Last(), true);
  356. TreeViewPtr->RequestScrollIntoView(TreeItems.Last());
  357. }
  358. else
  359. {
  360. // Could not even find the root path... skip
  361. }
  362. }
  363. else
  364. {
  365. // No path items... skip
  366. }
  367. }
  368. }
  369. void SExtPathView::ClearSelection()
  370. {
  371. // Prevent the selection changed delegate since the invoking code requested it
  372. FScopedPreventTreeItemChangedDelegate DelegatePrevention( SharedThis(this) );
  373. // If the selection was changed before all pending initial paths were found, stop attempting to select them
  374. PendingInitialPaths.Empty();
  375. // Clear the selection to start, then add the selected paths as they are found
  376. TreeViewPtr->ClearSelection();
  377. }
  378. int32 SExtPathView::GetNumPathsSelected() const
  379. {
  380. return TreeViewPtr->GetNumItemsSelected();
  381. }
  382. FString SExtPathView::GetSelectedPath() const
  383. {
  384. TArray<TSharedPtr<FTreeItem>> Items = TreeViewPtr->GetSelectedItems();
  385. if (Items.Num() > 0)
  386. {
  387. FString& FolderPath = Items[0]->FolderPath;
  388. return FolderPath;
  389. }
  390. return FString();
  391. }
  392. TArray<FString> SExtPathView::GetSelectedPaths() const
  393. {
  394. TArray<FString> RetArray;
  395. TArray<TSharedPtr<FTreeItem>> Items = TreeViewPtr->GetSelectedItems();
  396. for (int32 ItemIdx = 0; ItemIdx < Items.Num(); ++ItemIdx)
  397. {
  398. FString& FolderPath = Items[ItemIdx]->FolderPath;
  399. RetArray.Add(FolderPath);
  400. }
  401. return RetArray;
  402. }
  403. TSharedPtr<FTreeItem> SExtPathView::AddPath(const FString& InPath, const FString* RootPathPtr/* = nullptr*/, bool bUserNamed)
  404. {
  405. if ( !ensure(TreeViewPtr.IsValid()) )
  406. {
  407. // No tree view for some reason
  408. return TSharedPtr<FTreeItem>();
  409. }
  410. FString Path = InPath;
  411. FString RootPath;
  412. if (RootPathPtr != nullptr)
  413. {
  414. RootPath = *RootPathPtr;
  415. }
  416. TArray<FString> RootContentPaths;
  417. FExtContentBrowserSingleton::GetAssetRegistry().QueryRootContentPaths(RootContentPaths);
  418. TArray<FString> PathItemList;
  419. GetPathItemList(Path, /*{ RootPath }*/RootContentPaths, PathItemList, /*bIncludeRootPath*/ true);
  420. if ( PathItemList.Num() )
  421. {
  422. FString ContentRootPath = PathItemList[0];
  423. // There is at least one element in the path
  424. TSharedPtr<FTreeItem> CurrentItem;
  425. // Find the first item in the root items list
  426. for ( int32 RootItemIdx = 0; RootItemIdx < TreeRootItems.Num(); ++RootItemIdx )
  427. {
  428. if ( TreeRootItems[RootItemIdx]->FolderPath == ContentRootPath)
  429. {
  430. // Found the first item in the path
  431. CurrentItem = TreeRootItems[RootItemIdx];
  432. break;
  433. }
  434. }
  435. // Roots may or may not exist, add the root here if it doesn't
  436. if ( !CurrentItem.IsValid() )
  437. {
  438. CurrentItem = AddRootItem(ContentRootPath);
  439. }
  440. // Found or added the root item?
  441. if ( CurrentItem.IsValid() )
  442. {
  443. // Now add children as necessary
  444. const bool bDisplayEmpty = GetDefault<UExtContentBrowserSettings>()->DisplayEmptyFolders;
  445. const bool bDisplayDev = GetDefault<UExtContentBrowserSettings>()->GetDisplayDevelopersFolder();
  446. const bool bDisplayL10N = GetDefault<UExtContentBrowserSettings>()->GetDisplayL10NFolder();
  447. for ( int32 PathItemIdx = 1; PathItemIdx < PathItemList.Num(); ++PathItemIdx )
  448. {
  449. const FString& PathItemName = PathItemList[PathItemIdx];
  450. TSharedPtr<FTreeItem> ChildItem = CurrentItem->GetChild(PathItemName);
  451. // If it does not exist, Create the child item
  452. if ( !ChildItem.IsValid() )
  453. {
  454. const FString FolderName = PathItemName;
  455. const FString FolderPath = CurrentItem->FolderPath + "/" + PathItemName;
  456. ChildItem = MakeShareable( new FTreeItem(FText::FromString(FolderName), FolderName, FolderPath, RootPath, CurrentItem, bUserNamed) );
  457. CurrentItem->Children.Add(ChildItem);
  458. CurrentItem->RequestSortChildren();
  459. TreeViewPtr->RequestTreeRefresh();
  460. // If we have pending initial paths, and this path added the path, we should select it now
  461. if ( PendingInitialPaths.Num() > 0 && PendingInitialPaths.Contains(FolderPath) )
  462. {
  463. RecursiveExpandParents(ChildItem);
  464. TreeViewPtr->SetItemSelection(ChildItem, true);
  465. TreeViewPtr->RequestScrollIntoView(ChildItem);
  466. }
  467. }
  468. else
  469. {
  470. //If the child item does exist, ensure its folder path is correct (may differ when renaming parent folder)
  471. ChildItem->FolderPath = CurrentItem->FolderPath + "/" + PathItemName;
  472. }
  473. CurrentItem = ChildItem;
  474. }
  475. if ( bUserNamed && CurrentItem->Parent.IsValid() )
  476. {
  477. // If we were creating a new item, select it, scroll it into view, expand the parent
  478. RecursiveExpandParents(CurrentItem);
  479. TreeViewPtr->RequestScrollIntoView(CurrentItem);
  480. TreeViewPtr->SetSelection(CurrentItem);
  481. }
  482. else
  483. {
  484. CurrentItem->bNamingFolder = false;
  485. }
  486. // Root: Is Loading
  487. if (!CurrentItem->Parent.IsValid())
  488. {
  489. const bool bIsLoading = ExtContentBrowserUtils::IsFolderBackgroundGathering(CurrentItem->FolderPath);
  490. CurrentItem->bLoading = bIsLoading;
  491. }
  492. // Update Root's loading status
  493. if (CurrentItem->Parent.IsValid())
  494. {
  495. TSharedPtr<FTreeItem> RootOfCurrentItem = FindItemRecursive(RootPath);
  496. if (RootOfCurrentItem.IsValid())
  497. {
  498. const bool bIsLoading = ExtContentBrowserUtils::IsFolderBackgroundGathering(RootPath);
  499. RootOfCurrentItem->bLoading = bIsLoading;
  500. if (bIsLoading)
  501. {
  502. FName GatheringFolder = ExtContentBrowserUtils::GetCurrentGatheringFolder();
  503. if (GatheringFolder != NAME_None)
  504. {
  505. FString LoadingFolder = ExtContentBrowserUtils::GetCurrentGatheringFolder().ToString();
  506. if (LoadingFolder.StartsWith(RootPath))
  507. {
  508. LoadingFolder.RemoveFromStart(RootPath);
  509. LoadingFolder.RemoveFromStart(TEXT("/"));
  510. RootOfCurrentItem->LoadingStatus = LoadingFolder;
  511. }
  512. }
  513. }
  514. }
  515. }
  516. }
  517. return CurrentItem;
  518. }
  519. return TSharedPtr<FTreeItem>();
  520. }
  521. bool SExtPathView::RemovePath(const FString& Path)
  522. {
  523. if ( !ensure(TreeViewPtr.IsValid()) )
  524. {
  525. // No tree view for some reason
  526. return false;
  527. }
  528. if ( Path.IsEmpty() )
  529. {
  530. // There were no elements in the path, cannot remove nothing
  531. return false;
  532. }
  533. // Find the folder in the tree
  534. TSharedPtr<FTreeItem> ItemToRemove = FindItemRecursive(Path);
  535. if ( ItemToRemove.IsValid() )
  536. {
  537. // Found the folder to remove. Remove it.
  538. if ( ItemToRemove->Parent.IsValid() )
  539. {
  540. // Remove the folder from its parent's list
  541. ItemToRemove->Parent.Pin()->Children.Remove(ItemToRemove);
  542. }
  543. else
  544. {
  545. // This is a root item. Remove the folder from the root items list.
  546. TreeRootItems.Remove(ItemToRemove);
  547. }
  548. // Refresh the tree
  549. TreeViewPtr->RequestTreeRefresh();
  550. return true;
  551. }
  552. else
  553. {
  554. // Did not find the folder to remove
  555. return false;
  556. }
  557. }
  558. void SExtPathView::RenameFolder(const FString& FolderToRename)
  559. {
  560. TArray<TSharedPtr<FTreeItem>> Items = TreeViewPtr->GetSelectedItems();
  561. for (int32 ItemIdx = 0; ItemIdx < Items.Num(); ++ItemIdx)
  562. {
  563. TSharedPtr<FTreeItem>& Item = Items[ItemIdx];
  564. if (Item.IsValid())
  565. {
  566. if (Item->FolderPath == FolderToRename)
  567. {
  568. Item->bNamingFolder = true;
  569. TreeViewPtr->SetSelection(Item);
  570. TreeViewPtr->RequestScrollIntoView(Item);
  571. break;
  572. }
  573. }
  574. }
  575. }
  576. void SExtPathView::SyncToAssets( const TArray<FExtAssetData>& AssetDataList, const bool bAllowImplicitSync )
  577. {
  578. SyncToInternal(AssetDataList, TArray<FString>(), bAllowImplicitSync);
  579. }
  580. void SExtPathView::SyncToFolders( const TArray<FString>& FolderList, const bool bAllowImplicitSync )
  581. {
  582. SyncToInternal(TArray<FExtAssetData>(), FolderList, bAllowImplicitSync);
  583. }
  584. void SExtPathView::SyncTo( const FExtContentBrowserSelection& ItemSelection, const bool bAllowImplicitSync )
  585. {
  586. SyncToInternal(ItemSelection.SelectedAssets, ItemSelection.SelectedFolders, bAllowImplicitSync);
  587. }
  588. void SExtPathView::SyncToInternal( const TArray<FExtAssetData>& AssetDataList, const TArray<FString>& FolderPaths, const bool bAllowImplicitSync )
  589. {
  590. TArray<TSharedPtr<FTreeItem>> SyncTreeItems;
  591. // Clear the filter
  592. SearchBoxPtr->SetText(FText::GetEmpty());
  593. TSet<FString> PackagePaths = TSet<FString>(FolderPaths);
  594. for (const FExtAssetData& AssetData : AssetDataList)
  595. {
  596. FString PackagePath = AssetData.GetFolderPath();
  597. PackagePaths.Add(PackagePath);
  598. }
  599. for (const FString& PackagePath : PackagePaths)
  600. {
  601. if ( !PackagePath.IsEmpty() )
  602. {
  603. TSharedPtr<FTreeItem> Item = FindItemRecursive(PackagePath);
  604. if ( Item.IsValid() )
  605. {
  606. SyncTreeItems.Add(Item);
  607. }
  608. }
  609. }
  610. if ( SyncTreeItems.Num() > 0 )
  611. {
  612. if (bAllowImplicitSync)
  613. {
  614. // Prune the current selection so that we don't unnecessarily change the path which might disorientate the user.
  615. // If a parent tree item is currently selected we don't need to clear it and select the child
  616. auto SelectedTreeItems = TreeViewPtr->GetSelectedItems();
  617. for (int32 Index = 0; Index < SelectedTreeItems.Num(); ++Index)
  618. {
  619. // For each item already selected in the tree
  620. auto AlreadySelectedTreeItem = SelectedTreeItems[Index];
  621. if (!AlreadySelectedTreeItem.IsValid())
  622. {
  623. continue;
  624. }
  625. // Check to see if any of the items to sync are already synced
  626. for (int32 ToSyncIndex = SyncTreeItems.Num()-1; ToSyncIndex >= 0; --ToSyncIndex)
  627. {
  628. auto ToSyncItem = SyncTreeItems[ToSyncIndex];
  629. if (ToSyncItem == AlreadySelectedTreeItem || ToSyncItem->IsChildOf(*AlreadySelectedTreeItem.Get()))
  630. {
  631. // A parent is already selected
  632. SyncTreeItems.Pop();
  633. }
  634. else if (ToSyncIndex == 0)
  635. {
  636. // AlreadySelectedTreeItem is not required for SyncTreeItems, so deselect it
  637. TreeViewPtr->SetItemSelection(AlreadySelectedTreeItem, false);
  638. }
  639. }
  640. }
  641. }
  642. else
  643. {
  644. // Explicit sync so just clear the selection
  645. TreeViewPtr->ClearSelection();
  646. }
  647. // SyncTreeItems should now only contain items which aren't already shown explicitly or implicitly (as a child)
  648. for ( auto ItemIt = SyncTreeItems.CreateConstIterator(); ItemIt; ++ItemIt )
  649. {
  650. RecursiveExpandParents(*ItemIt);
  651. TreeViewPtr->SetItemSelection(*ItemIt, true);
  652. }
  653. // > 0 as some may have been popped off in the code above
  654. if (SyncTreeItems.Num() > 0)
  655. {
  656. // Scroll the first item into view if applicable
  657. TreeViewPtr->RequestScrollIntoView(SyncTreeItems[0]);
  658. }
  659. }
  660. }
  661. TSharedPtr<FTreeItem> SExtPathView::FindItemRecursive(const FString& Path) const
  662. {
  663. for (auto TreeItemIt = TreeRootItems.CreateConstIterator(); TreeItemIt; ++TreeItemIt)
  664. {
  665. if ( (*TreeItemIt)->FolderPath == Path)
  666. {
  667. // This root item is the path
  668. return *TreeItemIt;
  669. }
  670. // Try to find the item under this root
  671. TSharedPtr<FTreeItem> Item = (*TreeItemIt)->FindItemRecursive(Path);
  672. if ( Item.IsValid() )
  673. {
  674. // The item was found under this root
  675. return Item;
  676. }
  677. }
  678. return TSharedPtr<FTreeItem>();
  679. }
  680. void SExtPathView::ApplyHistoryData ( const FHistoryData& History )
  681. {
  682. #if ECB_WIP_HISTORY
  683. // Prevent the selection changed delegate because it would add more history when we are just setting a state
  684. FScopedPreventTreeItemChangedDelegate DelegatePrevention( SharedThis(this) );
  685. // Update paths
  686. TArray<FString> SelectedPaths;
  687. for (const FName& HistoryPath : History.SourcesData.PackagePaths)
  688. {
  689. SelectedPaths.Add(HistoryPath.ToString());
  690. }
  691. SetSelectedPaths(SelectedPaths);
  692. #endif
  693. }
  694. void SExtPathView::SaveSettings(const FString& IniFilename, const FString& IniSection, const FString& SettingsString) const
  695. {
  696. FString SelectedPathsString;
  697. TArray< TSharedPtr<FTreeItem> > PathItems = TreeViewPtr->GetSelectedItems();
  698. for ( auto PathIt = PathItems.CreateConstIterator(); PathIt; ++PathIt )
  699. {
  700. if ( SelectedPathsString.Len() > 0 )
  701. {
  702. SelectedPathsString += TEXT(",");
  703. }
  704. SelectedPathsString += (*PathIt)->FolderPath;
  705. }
  706. GConfig->SetString(*IniSection, *(SettingsString + TEXT(".SelectedPaths")), *SelectedPathsString, IniFilename);
  707. }
  708. void SExtPathView::LoadSettings(const FString& IniFilename, const FString& IniSection, const FString& SettingsString)
  709. {
  710. // Selected Paths
  711. FString SelectedPathsString;
  712. TArray<FString> NewSelectedPaths;
  713. if ( GConfig->GetString(*IniSection, *(SettingsString + TEXT(".SelectedPaths")), SelectedPathsString, IniFilename) )
  714. {
  715. SelectedPathsString.ParseIntoArray(NewSelectedPaths, TEXT(","), /*bCullEmpty*/true);
  716. }
  717. if ( NewSelectedPaths.Num() > 0 )
  718. {
  719. FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked<FAssetRegistryModule>("AssetRegistry");
  720. const bool bDiscoveringAssets = AssetRegistryModule.Get().IsLoadingAssets();
  721. if ( bDiscoveringAssets )
  722. {
  723. // Keep track if we changed at least one source so we know to fire the bulk selection changed delegate later
  724. bool bSelectedAtLeastOnePath = false;
  725. {
  726. // Prevent the selection changed delegate since we are selecting one path at a time. A bulk event will be fired later if needed.
  727. FScopedPreventTreeItemChangedDelegate DelegatePrevention( SharedThis(this) );
  728. // Clear any previously selected paths
  729. TreeViewPtr->ClearSelection();
  730. // If the selected paths is empty, the path was "All assets"
  731. // This should handle that case properly
  732. for (int32 PathIdx = 0; PathIdx < NewSelectedPaths.Num(); ++PathIdx)
  733. {
  734. const FString& Path = NewSelectedPaths[PathIdx];
  735. if ( ExplicitlyAddPathToSelection(Path) )
  736. {
  737. bSelectedAtLeastOnePath = true;
  738. }
  739. else
  740. {
  741. // If we could not initially select these paths, but are still discovering assets, add them to a pending list to select them later
  742. PendingInitialPaths.Add(Path);
  743. }
  744. }
  745. }
  746. if ( bSelectedAtLeastOnePath )
  747. {
  748. // Send the first selected item with the notification
  749. const TArray<TSharedPtr<FTreeItem>> SelectedItems = TreeViewPtr->GetSelectedItems();
  750. check(SelectedItems.Num() > 0);
  751. // Signal a single selection changed event to let any listeners know that paths have changed
  752. TreeSelectionChanged( SelectedItems[0], ESelectInfo::Direct );
  753. }
  754. }
  755. else
  756. {
  757. // If all assets are already discovered, just select paths the best we can
  758. SetSelectedPaths(NewSelectedPaths);
  759. // Send the first selected item with the notification
  760. const TArray<TSharedPtr<FTreeItem>> SelectedItems = TreeViewPtr->GetSelectedItems();
  761. if (SelectedItems.Num() > 0)
  762. {
  763. // Signal a single selection changed event to let any listeners know that paths have changed
  764. TreeSelectionChanged( SelectedItems[0], ESelectInfo::Direct );
  765. }
  766. }
  767. }
  768. }
  769. EActiveTimerReturnType SExtPathView::SetFocusPostConstruct( double InCurrentTime, float InDeltaTime )
  770. {
  771. FWidgetPath WidgetToFocusPath;
  772. FSlateApplication::Get().GeneratePathToWidgetUnchecked( SearchBoxPtr.ToSharedRef(), WidgetToFocusPath );
  773. FSlateApplication::Get().SetKeyboardFocus( WidgetToFocusPath, EFocusCause::SetDirectly );
  774. return EActiveTimerReturnType::Stop;
  775. }
  776. EActiveTimerReturnType SExtPathView::TriggerRepopulate(double InCurrentTime, float InDeltaTime)
  777. {
  778. Populate();
  779. return EActiveTimerReturnType::Stop;
  780. }
  781. TSharedPtr<SWidget> SExtPathView::MakePathViewContextMenu()
  782. {
  783. if (!bAllowContextMenu || !OnGetFolderContextMenu.IsBound())
  784. {
  785. return nullptr;
  786. }
  787. const TArray<FString> SelectedPaths = GetSelectedPaths();
  788. if (SelectedPaths.Num() == 0)
  789. {
  790. return nullptr;
  791. }
  792. return OnGetFolderContextMenu.Execute(SelectedPaths, OnGetPathContextMenuExtender, NULL);
  793. }
  794. void SExtPathView::OnCreateNewFolder(const FString& FolderName, const FString& FolderPath)
  795. {
  796. AddPath(FolderPath / FolderName);
  797. }
  798. bool SExtPathView::ExplicitlyAddPathToSelection(const FString& Path)
  799. {
  800. if ( !ensure(TreeViewPtr.IsValid()) )
  801. {
  802. return false;
  803. }
  804. TArray<FString> PathItemList;
  805. Path.ParseIntoArray(PathItemList, TEXT("/"), /*InCullEmpty=*/true);
  806. if ( PathItemList.Num() )
  807. {
  808. // There is at least one element in the path
  809. TSharedPtr<FTreeItem> RootItem;
  810. // Find the first item in the root items list
  811. for ( int32 RootItemIdx = 0; RootItemIdx < TreeRootItems.Num(); ++RootItemIdx )
  812. {
  813. if ( TreeRootItems[RootItemIdx]->FolderPath == PathItemList[0] )
  814. {
  815. // Found the first item in the path
  816. RootItem = TreeRootItems[RootItemIdx];
  817. break;
  818. }
  819. }
  820. // If found in the root items list, try to find the item matching the path
  821. if ( RootItem.IsValid() )
  822. {
  823. TSharedPtr<FTreeItem> FoundItem = RootItem->FindItemRecursive(Path);
  824. if ( FoundItem.IsValid() )
  825. {
  826. // Set the selection to the closest found folder and scroll it into view
  827. RecursiveExpandParents(FoundItem);
  828. TreeViewPtr->SetItemSelection(FoundItem, true);
  829. TreeViewPtr->RequestScrollIntoView(FoundItem);
  830. return true;
  831. }
  832. }
  833. }
  834. return false;
  835. }
  836. bool SExtPathView::ShouldAllowTreeItemChangedDelegate() const
  837. {
  838. return PreventTreeItemChangedDelegateCount == 0;
  839. }
  840. void SExtPathView::RecursiveExpandParents(const TSharedPtr<FTreeItem>& Item)
  841. {
  842. if ( Item->Parent.IsValid() )
  843. {
  844. RecursiveExpandParents(Item->Parent.Pin());
  845. TreeViewPtr->SetItemExpansion(Item->Parent.Pin(), true);
  846. }
  847. }
  848. TSharedPtr<struct FTreeItem> SExtPathView::AddRootItem( const FString& InFolderName )
  849. {
  850. // Make sure the item is not already in the list
  851. for ( int32 RootItemIdx = 0; RootItemIdx < TreeRootItems.Num(); ++RootItemIdx )
  852. {
  853. if ( TreeRootItems[RootItemIdx]->FolderName == InFolderName )
  854. {
  855. // The root to add was already in the list return it here
  856. return TreeRootItems[RootItemIdx];
  857. }
  858. }
  859. TSharedPtr<struct FTreeItem> NewItem = nullptr;
  860. const bool bIsValidRootFolder = true;
  861. if (bIsValidRootFolder)
  862. {
  863. const FText DisplayName = ExtContentBrowserUtils::GetRootDirDisplayName(InFolderName);
  864. NewItem = MakeShareable( new FTreeItem(DisplayName, /*FolderName*/ InFolderName, /*InFolderPath*/ InFolderName, /*InRootFolderPath*/InFolderName, TSharedPtr<FTreeItem>()));
  865. TreeRootItems.Add( NewItem );
  866. TreeViewPtr->RequestTreeRefresh();
  867. }
  868. return NewItem;
  869. }
  870. TSharedRef<ITableRow> SExtPathView::GenerateTreeRow( TSharedPtr<FTreeItem> TreeItem, const TSharedRef<STableViewBase>& OwnerTable )
  871. {
  872. check(TreeItem.IsValid());
  873. return
  874. SNew( STableRow< TSharedPtr<FTreeItem> >, OwnerTable )
  875. .OnDragDetected( this, &SExtPathView::OnFolderDragDetected )
  876. [
  877. SNew(SExtAssetTreeItem)
  878. .TreeItem(TreeItem)
  879. .OnAssetsOrPathsDragDropped(this, &SExtPathView::TreeAssetsOrPathsDropped)
  880. .OnFilesDragDropped(this, &SExtPathView::TreeFilesDropped)
  881. .IsItemExpanded(this, &SExtPathView::IsTreeItemExpanded, TreeItem)
  882. .HighlightText(this, &SExtPathView::GetHighlightText)
  883. .IsSelected(this, &SExtPathView::IsTreeItemSelected, TreeItem)
  884. ];
  885. }
  886. void SExtPathView::TreeItemScrolledIntoView( TSharedPtr<FTreeItem> TreeItem, const TSharedPtr<ITableRow>& Widget )
  887. {
  888. if ( TreeItem->bNamingFolder && Widget.IsValid() && Widget->GetContent().IsValid() )
  889. {
  890. TreeItem->OnRenamedRequestEvent.Broadcast();
  891. }
  892. }
  893. void SExtPathView::GetChildrenForTree( TSharedPtr< FTreeItem > TreeItem, TArray< TSharedPtr<FTreeItem> >& OutChildren )
  894. {
  895. TreeItem->SortChildrenIfNeeded();
  896. OutChildren = TreeItem->Children;
  897. }
  898. void SExtPathView::SetTreeItemExpansionRecursive( TSharedPtr< FTreeItem > TreeItem, bool bInExpansionState )
  899. {
  900. TreeViewPtr->SetItemExpansion(TreeItem, bInExpansionState);
  901. // Recursively go through the children.
  902. for(auto It = TreeItem->Children.CreateIterator(); It; ++It)
  903. {
  904. SetTreeItemExpansionRecursive( *It, bInExpansionState );
  905. }
  906. }
  907. void SExtPathView::TreeSelectionChanged( TSharedPtr< FTreeItem > TreeItem, ESelectInfo::Type /*SelectInfo*/ )
  908. {
  909. if ( ShouldAllowTreeItemChangedDelegate() )
  910. {
  911. const TArray<TSharedPtr<FTreeItem>> SelectedItems = TreeViewPtr->GetSelectedItems();
  912. LastSelectedPaths.Empty();
  913. for (int32 ItemIdx = 0; ItemIdx < SelectedItems.Num(); ++ItemIdx)
  914. {
  915. const TSharedPtr<FTreeItem> Item = SelectedItems[ItemIdx];
  916. if ( !ensure(Item.IsValid()) )
  917. {
  918. // All items must exist
  919. continue;
  920. }
  921. // Keep track of the last paths that we broadcasted for selection reasons when filtering
  922. LastSelectedPaths.Add(Item->FolderPath);
  923. }
  924. if (OnPathSelected.IsBound() )
  925. {
  926. if ( TreeItem.IsValid() )
  927. {
  928. OnPathSelected.Execute(TreeItem->FolderPath);
  929. }
  930. else
  931. {
  932. OnPathSelected.Execute(TEXT(""));
  933. }
  934. }
  935. }
  936. if (TreeItem.IsValid())
  937. {
  938. // Prioritize the asset registry scan for the selected path
  939. FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked<FAssetRegistryModule>(TEXT("AssetRegistry"));
  940. AssetRegistryModule.Get().PrioritizeSearchPath(TreeItem->FolderPath / TEXT(""));
  941. }
  942. }
  943. void SExtPathView::TreeExpansionChanged( TSharedPtr< FTreeItem > TreeItem, bool bIsExpanded )
  944. {
  945. if ( ShouldAllowTreeItemChangedDelegate() )
  946. {
  947. TSet<TSharedPtr<FTreeItem>> ExpandedItemSet;
  948. TreeViewPtr->GetExpandedItems(ExpandedItemSet);
  949. const TArray<TSharedPtr<FTreeItem>> ExpandedItems = ExpandedItemSet.Array();
  950. LastExpandedPaths.Empty();
  951. for (int32 ItemIdx = 0; ItemIdx < ExpandedItems.Num(); ++ItemIdx)
  952. {
  953. const TSharedPtr<FTreeItem> Item = ExpandedItems[ItemIdx];
  954. if ( !ensure(Item.IsValid()) )
  955. {
  956. // All items must exist
  957. continue;
  958. }
  959. // Keep track of the last paths that we broadcasted for expansion reasons when filtering
  960. LastExpandedPaths.Add(Item->FolderPath);
  961. }
  962. }
  963. }
  964. void SExtPathView::OnAssetTreeSearchBoxChanged( const FText& InSearchText )
  965. {
  966. SearchBoxFolderFilter->SetRawFilterText( InSearchText );
  967. SearchBoxPtr->SetError( SearchBoxFolderFilter->GetFilterErrorText() );
  968. }
  969. void SExtPathView::OnAssetTreeSearchBoxCommitted(const FText& InSearchText, ETextCommit::Type InCommitType)
  970. {
  971. if (InCommitType == ETextCommit::OnCleared)
  972. {
  973. // Clear the search box and the filters
  974. SearchBoxPtr->SetText(FText::GetEmpty());
  975. OnAssetTreeSearchBoxChanged(FText::GetEmpty());
  976. FSlateApplication::Get().ClearKeyboardFocus(EFocusCause::Cleared);
  977. }
  978. }
  979. void SExtPathView::FilterUpdated()
  980. {
  981. Populate();
  982. }
  983. FText SExtPathView::GetHighlightText() const
  984. {
  985. return SearchBoxFolderFilter->GetRawFilterText();
  986. }
  987. void SExtPathView::Populate()
  988. {
  989. ECB_LOG(Display, TEXT("[SExtPathView] Populate"));
  990. // Don't allow the selection changed delegate to be fired here
  991. FScopedPreventTreeItemChangedDelegate DelegatePrevention( SharedThis(this) );
  992. // Clear all root items and clear selection
  993. TreeRootItems.Empty();
  994. TreeViewPtr->ClearSelection();
  995. const bool bFilteringByText = !SearchBoxFolderFilter->GetRawFilterText().IsEmpty();
  996. TArray<FString> RootContentPaths;
  997. FExtContentBrowserSingleton::GetAssetRegistry().QueryRootContentPaths(RootContentPaths);
  998. // Add default root folders to the asset tree if there's no filtering
  999. if ( !bFilteringByText )
  1000. {
  1001. for( TArray<FString>::TConstIterator RootPathIt( RootContentPaths ); RootPathIt; ++RootPathIt )
  1002. {
  1003. // Strip off any trailing forward slashes
  1004. FString CleanRootPathName = *RootPathIt;
  1005. while( CleanRootPathName.EndsWith( TEXT( "/" ) ) )
  1006. {
  1007. CleanRootPathName = CleanRootPathName.Mid( 0, CleanRootPathName.Len() - 1 );
  1008. }
  1009. AddPath(CleanRootPathName, &CleanRootPathName);
  1010. }
  1011. }
  1012. for (const FString& RootContentPath : RootContentPaths)
  1013. {
  1014. if (FExtContentBrowserSingleton::GetAssetRegistry().IsFolderBackgroundGathering(*RootContentPath))
  1015. {
  1016. //continue;
  1017. }
  1018. // Get all paths
  1019. TSet<FName> SubPaths;
  1020. FExtContentBrowserSingleton::GetAssetRegistry().GetCachedSubPaths/*GetOrCacheSubPaths*//*safer*/(*RootContentPath, SubPaths, /*bRecursively = */ true);
  1021. // Add all paths
  1022. for (const FName& SubPath : SubPaths)
  1023. {
  1024. const FString Path = SubPath.ToString();
  1025. // by sending the whole path we deliberately include any children of successful hits in the filtered list.
  1026. if (SearchBoxFolderFilter->PassesFilter(Path))
  1027. {
  1028. TSharedPtr<FTreeItem> Item = AddPath(Path, &RootContentPath);
  1029. if (Item.IsValid())
  1030. {
  1031. const bool bSelectedItem = LastSelectedPaths.Contains(Item->FolderPath);
  1032. const bool bExpandedItem = LastExpandedPaths.Contains(Item->FolderPath);
  1033. if (bFilteringByText || bSelectedItem)
  1034. {
  1035. RecursiveExpandParents(Item);
  1036. }
  1037. if (bSelectedItem)
  1038. {
  1039. // Tree items that match the last broadcasted paths should be re-selected them after they are added
  1040. if (!TreeViewPtr->IsItemSelected(Item))
  1041. {
  1042. TreeViewPtr->SetItemSelection(Item, true);
  1043. }
  1044. TreeViewPtr->RequestScrollIntoView(Item);
  1045. }
  1046. if (bExpandedItem)
  1047. {
  1048. // Tree items that were previously expanded should be re-expanded when repopulating
  1049. if (!TreeViewPtr->IsItemExpanded(Item))
  1050. {
  1051. TreeViewPtr->SetItemExpansion(Item, true);
  1052. //RecursiveExpandParents(Item);
  1053. }
  1054. }
  1055. }
  1056. }
  1057. }
  1058. }
  1059. SortRootItems();
  1060. }
  1061. void SExtPathView::SortRootItems()
  1062. {
  1063. // First sort the root items by their display name, but also making sure that content to appears before classes
  1064. TreeRootItems.Sort([](const TSharedPtr<FTreeItem>& One, const TSharedPtr<FTreeItem>& Two) -> bool
  1065. {
  1066. static const FString ClassesPrefix = TEXT("Classes_");
  1067. FString OneModuleName = One->FolderName;
  1068. const bool bOneIsClass = OneModuleName.StartsWith(ClassesPrefix);
  1069. if(bOneIsClass)
  1070. {
  1071. OneModuleName = OneModuleName.Mid(ClassesPrefix.Len());
  1072. }
  1073. FString TwoModuleName = Two->FolderName;
  1074. const bool bTwoIsClass = TwoModuleName.StartsWith(ClassesPrefix);
  1075. if(bTwoIsClass)
  1076. {
  1077. TwoModuleName = TwoModuleName.Mid(ClassesPrefix.Len());
  1078. }
  1079. // We want to sort content before classes if both items belong to the same module
  1080. if(OneModuleName == TwoModuleName)
  1081. {
  1082. if(!bOneIsClass && bTwoIsClass)
  1083. {
  1084. return true;
  1085. }
  1086. return false;
  1087. }
  1088. return One->DisplayName.ToString() < Two->DisplayName.ToString();
  1089. });
  1090. // 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
  1091. // The array below is in the inverse order as we iterate through and move each match to the beginning of the root items array
  1092. static const FString InverseSortOrder[] = {
  1093. TEXT("Classes_Engine"),
  1094. TEXT("Engine"),
  1095. TEXT("Classes_Game"),
  1096. TEXT("Game"),
  1097. };
  1098. for(const FString& SortItem : InverseSortOrder)
  1099. {
  1100. const int32 FoundItemIndex = TreeRootItems.IndexOfByPredicate([&SortItem](const TSharedPtr<FTreeItem>& TreeItem) -> bool
  1101. {
  1102. return TreeItem->FolderName == SortItem;
  1103. });
  1104. if(FoundItemIndex != INDEX_NONE)
  1105. {
  1106. TSharedPtr<FTreeItem> ItemToMove = TreeRootItems[FoundItemIndex];
  1107. TreeRootItems.RemoveAt(FoundItemIndex);
  1108. TreeRootItems.Insert(ItemToMove, 0);
  1109. }
  1110. }
  1111. TreeViewPtr->RequestTreeRefresh();
  1112. }
  1113. void SExtPathView::PopulateFolderSearchStrings( const FString& FolderName, OUT TArray< FString >& OutSearchStrings ) const
  1114. {
  1115. OutSearchStrings.Add( FolderName );
  1116. }
  1117. FReply SExtPathView::OnFolderDragDetected(const FGeometry& Geometry, const FPointerEvent& MouseEvent)
  1118. {
  1119. if ( MouseEvent.IsMouseButtonDown( EKeys::LeftMouseButton ) )
  1120. {
  1121. TArray<TSharedPtr<FTreeItem>> SelectedItems = TreeViewPtr->GetSelectedItems();
  1122. if (SelectedItems.Num())
  1123. {
  1124. TArray<FString> PathNames;
  1125. for ( auto ItemIt = SelectedItems.CreateConstIterator(); ItemIt; ++ItemIt )
  1126. {
  1127. PathNames.Add((*ItemIt)->FolderPath);
  1128. }
  1129. return FReply::Handled().BeginDragDrop(FAssetDragDropOp::New(PathNames));
  1130. }
  1131. }
  1132. return FReply::Unhandled();
  1133. }
  1134. bool SExtPathView::FolderAlreadyExists(const TSharedPtr< FTreeItem >& TreeItem, TSharedPtr< FTreeItem >& ExistingItem)
  1135. {
  1136. ExistingItem.Reset();
  1137. if ( TreeItem.IsValid() )
  1138. {
  1139. if ( TreeItem->Parent.IsValid() )
  1140. {
  1141. // This item has a parent, try to find it in its parent's children
  1142. TSharedPtr<FTreeItem> ParentItem = TreeItem->Parent.Pin();
  1143. for ( auto ChildIt = ParentItem->Children.CreateConstIterator(); ChildIt; ++ChildIt )
  1144. {
  1145. const TSharedPtr<FTreeItem>& Child = *ChildIt;
  1146. if ( Child != TreeItem && Child->FolderName == TreeItem->FolderName )
  1147. {
  1148. // The item is in its parent already
  1149. ExistingItem = Child;
  1150. break;
  1151. }
  1152. }
  1153. }
  1154. else
  1155. {
  1156. // This item is part of the root set
  1157. for ( auto RootIt = TreeRootItems.CreateConstIterator(); RootIt; ++RootIt )
  1158. {
  1159. const TSharedPtr<FTreeItem>& Root = *RootIt;
  1160. if ( Root != TreeItem && Root->FolderName == TreeItem->FolderName )
  1161. {
  1162. // The item is part of the root set already
  1163. ExistingItem = Root;
  1164. break;
  1165. }
  1166. }
  1167. }
  1168. }
  1169. return ExistingItem.IsValid();
  1170. }
  1171. void SExtPathView::RemoveFolderItem(const TSharedPtr< FTreeItem >& TreeItem)
  1172. {
  1173. if ( TreeItem.IsValid() )
  1174. {
  1175. if ( TreeItem->Parent.IsValid() )
  1176. {
  1177. // Remove this item from it's parent's list
  1178. TreeItem->Parent.Pin()->Children.Remove(TreeItem);
  1179. }
  1180. else
  1181. {
  1182. // This was a root node, remove from the root list
  1183. TreeRootItems.Remove(TreeItem);
  1184. }
  1185. TreeViewPtr->RequestTreeRefresh();
  1186. }
  1187. }
  1188. void SExtPathView::TreeAssetsOrPathsDropped(const TArray<FAssetData>& AssetList, const TArray<FString>& AssetPaths, const TSharedPtr<FTreeItem>& TreeItem)
  1189. {
  1190. DragDropHandler::HandleDropOnAssetFolder(
  1191. SharedThis(this),
  1192. AssetList,
  1193. AssetPaths,
  1194. TreeItem->FolderPath,
  1195. TreeItem->DisplayName,
  1196. DragDropHandler::FExecuteCopyOrMove::CreateSP(this, &SExtPathView::ExecuteTreeDropCopy),
  1197. DragDropHandler::FExecuteCopyOrMove::CreateSP(this, &SExtPathView::ExecuteTreeDropMove),
  1198. DragDropHandler::FExecuteCopyOrMove::CreateSP(this, &SExtPathView::ExecuteTreeDropAdvancedCopy)
  1199. );
  1200. }
  1201. void SExtPathView::TreeFilesDropped(const TArray<FString>& FileNames, const TSharedPtr<FTreeItem>& TreeItem)
  1202. {
  1203. FAssetToolsModule& AssetToolsModule = FModuleManager::Get().LoadModuleChecked<FAssetToolsModule>("AssetTools");
  1204. AssetToolsModule.Get().ImportAssets( FileNames, TreeItem->FolderPath );
  1205. }
  1206. bool SExtPathView::IsTreeItemExpanded(TSharedPtr<FTreeItem> TreeItem) const
  1207. {
  1208. return TreeViewPtr->IsItemExpanded(TreeItem);
  1209. }
  1210. bool SExtPathView::IsTreeItemSelected(TSharedPtr<FTreeItem> TreeItem) const
  1211. {
  1212. return TreeViewPtr->IsItemSelected(TreeItem);
  1213. }
  1214. void SExtPathView::ExecuteTreeDropCopy(TArray<FAssetData> AssetList, TArray<FString> AssetPaths, FString DestinationPath)
  1215. {
  1216. if (AssetList.Num() > 0)
  1217. {
  1218. TArray<UObject*> DroppedObjects;
  1219. ExtContentBrowserUtils::GetObjectsInAssetData(AssetList, DroppedObjects);
  1220. ExtContentBrowserUtils::CopyAssets(DroppedObjects, DestinationPath);
  1221. }
  1222. if (AssetPaths.Num() > 0 && ExtContentBrowserUtils::CopyFolders(AssetPaths, DestinationPath))
  1223. {
  1224. TSharedPtr<FTreeItem> RootItem = FindItemRecursive(DestinationPath);
  1225. if (RootItem.IsValid())
  1226. {
  1227. TreeViewPtr->SetItemExpansion(RootItem, true);
  1228. // Select all the new folders
  1229. TreeViewPtr->ClearSelection();
  1230. for (const FString& AssetPath : AssetPaths)
  1231. {
  1232. const FString SubFolderName = FPackageName::GetLongPackageAssetName(AssetPath);
  1233. const FString NewPath = DestinationPath + TEXT("/") + SubFolderName;
  1234. TSharedPtr<FTreeItem> Item = FindItemRecursive(NewPath);
  1235. if (Item.IsValid())
  1236. {
  1237. TreeViewPtr->SetItemSelection(Item, true);
  1238. TreeViewPtr->RequestScrollIntoView(Item);
  1239. }
  1240. }
  1241. }
  1242. }
  1243. }
  1244. void SExtPathView::ExecuteTreeDropMove(TArray<FAssetData> AssetList, TArray<FString> AssetPaths, FString DestinationPath)
  1245. {
  1246. #if ECB_LEGACY
  1247. if (AssetList.Num() > 0)
  1248. {
  1249. TArray<UObject*> DroppedObjects;
  1250. ContentBrowserUtils::GetObjectsInAssetData(AssetList, DroppedObjects);
  1251. ContentBrowserUtils::MoveAssets(DroppedObjects, DestinationPath);
  1252. }
  1253. // Prepare to fixup any asset paths that are favorites
  1254. TArray<FMovedContentFolder> MovedFolders;
  1255. for (const FString& OldPath : AssetPaths)
  1256. {
  1257. const FString SubFolderName = FPackageName::GetLongPackageAssetName(OldPath);
  1258. const FString NewPath = DestinationPath + TEXT("/") + SubFolderName;
  1259. MovedFolders.Add(FMovedContentFolder(OldPath, NewPath));
  1260. }
  1261. if (AssetPaths.Num() > 0 && ContentBrowserUtils::MoveFolders(AssetPaths, DestinationPath))
  1262. {
  1263. TSharedPtr<FTreeItem> RootItem = FindItemRecursive(DestinationPath);
  1264. if (RootItem.IsValid())
  1265. {
  1266. TreeViewPtr->SetItemExpansion(RootItem, true);
  1267. // Select all the new folders
  1268. TreeViewPtr->ClearSelection();
  1269. for (const FString& AssetPath : AssetPaths)
  1270. {
  1271. const FString SubFolderName = FPackageName::GetLongPackageAssetName(AssetPath);
  1272. const FString NewPath = DestinationPath + TEXT("/") + SubFolderName;
  1273. TSharedPtr<FTreeItem> Item = FindItemRecursive(NewPath);
  1274. if (Item.IsValid())
  1275. {
  1276. TreeViewPtr->SetItemSelection(Item, true);
  1277. TreeViewPtr->RequestScrollIntoView(Item);
  1278. }
  1279. }
  1280. }
  1281. OnFolderPathChanged.ExecuteIfBound(MovedFolders);
  1282. }
  1283. #endif
  1284. }
  1285. void SExtPathView::GetPathItemList(const FString& InPath, const TArray<FString>& InRootPaths, TArray<FString>& OutPathItemList, bool bIncludeRootPath) const
  1286. {
  1287. bool bFoundRootPath = false;
  1288. FString RootPath;
  1289. FString RelativePath;
  1290. for (int32 Index = 0; Index < InRootPaths.Num(); ++Index)
  1291. {
  1292. RootPath = InRootPaths[Index];
  1293. if (InPath.Find(*RootPath) == 0)
  1294. {
  1295. bFoundRootPath = true;
  1296. RelativePath = InPath.Mid(FCString::Strlen(*RootPath));
  1297. break;
  1298. }
  1299. }
  1300. RelativePath.ParseIntoArray(OutPathItemList, TEXT("/"), /*InCullEmpty=*/true);
  1301. if (bFoundRootPath && !RootPath.IsEmpty() && bIncludeRootPath)
  1302. {
  1303. OutPathItemList.Insert(RootPath, 0);
  1304. }
  1305. }
  1306. void SExtPathView::ExecuteTreeDropAdvancedCopy(TArray<FAssetData> AssetList, TArray<FString> AssetPaths, FString DestinationPath)
  1307. {
  1308. ExtContentBrowserUtils::BeginAdvancedCopyPackages(AssetList, AssetPaths, DestinationPath);
  1309. }
  1310. void SExtPathView::OnAssetRegistryPathAdded(const FString& Path)
  1311. {
  1312. // by sending the whole path we deliberately include any children
  1313. // of successful hits in the filtered list.
  1314. if ( SearchBoxFolderFilter->PassesFilter( Path ) )
  1315. {
  1316. AddPath(Path);
  1317. }
  1318. }
  1319. void SExtPathView::OnAssetRegistryPathRemoved(const FString& Path)
  1320. {
  1321. // by sending the whole path we deliberately include any children
  1322. // of successful hits in the filtered list.
  1323. if ( SearchBoxFolderFilter->PassesFilter( Path ) )
  1324. {
  1325. RemovePath(Path);
  1326. }
  1327. }
  1328. void SExtPathView::OnAssetRegistryRootPathAdded(const FString& Path)
  1329. {
  1330. Populate();
  1331. }
  1332. void SExtPathView::OnAssetRegistryRootPathRemoved(const FString& Path)
  1333. {
  1334. Populate();
  1335. ClearSelection();
  1336. }
  1337. void SExtPathView::OnAssetRegistryRootPathUpdated()
  1338. {
  1339. Populate();
  1340. }
  1341. void SExtPathView::OnAssetRegistryFolderStartGathering(const TArray<FString>& Paths)
  1342. {
  1343. //Populate();
  1344. //ClearSelection();
  1345. }
  1346. void SExtPathView::OnAssetRegistryFolderFinishGathering(const FString& InPath, const FString& InRootPath)
  1347. {
  1348. //Populate();
  1349. if (SearchBoxFolderFilter->PassesFilter(InPath))
  1350. {
  1351. AddPath(InPath, &InRootPath);
  1352. TArray<FString> SelectedPath = GetSelectedPaths();
  1353. if (SelectedPath.Contains(InPath) && OnPathSelected.IsBound())
  1354. {
  1355. // Refresh
  1356. OnPathSelected.Execute(InPath);
  1357. }
  1358. }
  1359. }
  1360. void SExtPathView::OnAssetRegistrySearchCompleted()
  1361. {
  1362. // If there were any more initial paths, they no longer exist so clear them now.
  1363. PendingInitialPaths.Empty();
  1364. }
  1365. void SExtPathView::OnFolderPopulated(const FString& Path)
  1366. {
  1367. OnAssetRegistryPathAdded(Path);
  1368. }
  1369. void SExtPathView::OnContentPathMountedOrDismounted( const FString& AssetPath, const FString& FilesystemPath )
  1370. {
  1371. /**
  1372. * Hotfix
  1373. * For some reason this widget sometime outlive the slate application shutdown
  1374. * Validating that Slate application base is initialized will at least avoid the possible crash
  1375. */
  1376. if ( FSlateApplicationBase::IsInitialized() )
  1377. {
  1378. // A new content path has appeared, so we should refresh out root set of paths
  1379. RegisterActiveTimer(0.f, FWidgetActiveTimerDelegate::CreateSP(this, &SExtPathView::TriggerRepopulate));
  1380. }
  1381. }
  1382. void SExtPathView::OnClassHierarchyUpdated()
  1383. {
  1384. // The class hierarchy has changed in some way, so we need to refresh our set of paths
  1385. RegisterActiveTimer(0.f, FWidgetActiveTimerDelegate::CreateSP(this, &SExtPathView::TriggerRepopulate));
  1386. }
  1387. void SExtPathView::HandleSettingChanged(FName PropertyName)
  1388. {
  1389. #if ECB_TODO
  1390. if ((PropertyName == GET_MEMBER_NAME_CHECKED(UExtContentBrowserSettings, DisplayEmptyFolders)) ||
  1391. (PropertyName == "DisplayDevelopersFolder") ||
  1392. (PropertyName == "DisplayEngineFolder") ||
  1393. (PropertyName == "DisplayPluginFolders") ||
  1394. (PropertyName == "DisplayL10NFolder") ||
  1395. (PropertyName == NAME_None)) // @todo: Needed if PostEditChange was called manually, for now
  1396. {
  1397. TSharedRef<FEmptyFolderVisibilityManager> EmptyFolderVisibilityManager = FContentBrowserSingleton::Get().GetEmptyFolderVisibilityManager();
  1398. // If the dev or engine folder is no longer visible but we're inside it...
  1399. const bool bDisplayEmpty = GetDefault<UExtContentBrowserSettings>()->DisplayEmptyFolders;
  1400. const bool bDisplayDev = GetDefault<UExtContentBrowserSettings>()->GetDisplayDevelopersFolder();
  1401. const bool bDisplayEngine = GetDefault<UExtContentBrowserSettings>()->GetDisplayEngineFolder();
  1402. const bool bDisplayPlugins = GetDefault<UExtContentBrowserSettings>()->GetDisplayPluginFolders();
  1403. const bool bDisplayL10N = GetDefault<UExtContentBrowserSettings>()->GetDisplayL10NFolder();
  1404. if (!bDisplayEmpty || !bDisplayDev || !bDisplayEngine || !bDisplayPlugins || !bDisplayL10N)
  1405. {
  1406. const FString OldSelectedPath = GetSelectedPath();
  1407. const ContentBrowserUtils::ECBFolderCategory OldFolderCategory = ContentBrowserUtils::GetFolderCategory(OldSelectedPath);
  1408. if ((!bDisplayEmpty && !EmptyFolderVisibilityManager->ShouldShowPath(OldSelectedPath)) ||
  1409. (!bDisplayDev && OldFolderCategory == ContentBrowserUtils::ECBFolderCategory::DeveloperContent) ||
  1410. (!bDisplayEngine && (OldFolderCategory == ContentBrowserUtils::ECBFolderCategory::EngineContent || OldFolderCategory == ContentBrowserUtils::ECBFolderCategory::EngineClasses)) ||
  1411. (!bDisplayPlugins && (OldFolderCategory == ContentBrowserUtils::ECBFolderCategory::PluginContent || OldFolderCategory == ContentBrowserUtils::ECBFolderCategory::PluginClasses)) ||
  1412. (!bDisplayL10N && ContentBrowserUtils::IsLocalizationFolder(OldSelectedPath)))
  1413. {
  1414. // Set the folder back to the root, and refresh the contents
  1415. TSharedPtr<FTreeItem> GameRoot = FindItemRecursive(TEXT("/Game"));
  1416. if ( GameRoot.IsValid() )
  1417. {
  1418. TreeViewPtr->SetSelection(GameRoot);
  1419. }
  1420. else
  1421. {
  1422. TreeViewPtr->ClearSelection();
  1423. }
  1424. }
  1425. }
  1426. // Update our path view so that it can include/exclude the dev folder
  1427. Populate();
  1428. // If the dev or engine folder has become visible and we're inside it...
  1429. if (bDisplayDev || bDisplayEngine || bDisplayPlugins || bDisplayL10N)
  1430. {
  1431. const FString NewSelectedPath = GetSelectedPath();
  1432. const ContentBrowserUtils::ECBFolderCategory NewFolderCategory = ContentBrowserUtils::GetFolderCategory(NewSelectedPath);
  1433. if ((bDisplayDev && NewFolderCategory == ContentBrowserUtils::ECBFolderCategory::DeveloperContent) ||
  1434. (bDisplayEngine && (NewFolderCategory == ContentBrowserUtils::ECBFolderCategory::EngineContent || NewFolderCategory == ContentBrowserUtils::ECBFolderCategory::EngineClasses)) ||
  1435. (bDisplayPlugins && (NewFolderCategory == ContentBrowserUtils::ECBFolderCategory::PluginContent || NewFolderCategory == ContentBrowserUtils::ECBFolderCategory::PluginClasses)) ||
  1436. (bDisplayL10N && ContentBrowserUtils::IsLocalizationFolder(NewSelectedPath)))
  1437. {
  1438. // Refresh the contents
  1439. OnPathSelected.ExecuteIfBound(NewSelectedPath);
  1440. }
  1441. }
  1442. }
  1443. #endif
  1444. }
  1445. void SFavoritePathView::Construct(const FArguments& InArgs)
  1446. {
  1447. SAssignNew(TreeViewPtr, STreeView< TSharedPtr<FTreeItem> >)
  1448. .TreeItemsSource(&TreeRootItems)
  1449. .OnGetChildren(this, &SFavoritePathView::GetChildrenForTree)
  1450. .OnGenerateRow(this, &SFavoritePathView::GenerateTreeRow)
  1451. .OnItemScrolledIntoView(this, &SFavoritePathView::TreeItemScrolledIntoView)
  1452. .ItemHeight(18)
  1453. .SelectionMode(InArgs._SelectionMode)
  1454. .OnSelectionChanged(this, &SFavoritePathView::TreeSelectionChanged)
  1455. .OnContextMenuOpening(this, &SFavoritePathView::MakePathViewContextMenu)
  1456. .ClearSelectionOnClick(false);
  1457. SExtPathView::Construct(InArgs);
  1458. }
  1459. void SFavoritePathView::Populate()
  1460. {
  1461. // Don't allow the selection changed delegate to be fired here
  1462. FScopedPreventTreeItemChangedDelegate DelegatePrevention(SharedThis(this));
  1463. // Clear all root items and clear selection
  1464. TreeRootItems.Empty();
  1465. TreeViewPtr->ClearSelection();
  1466. const TArray<FString> FavoritePaths = ExtContentBrowserUtils::GetFavoriteFolders();
  1467. // we have a text filter, expand all parents of matching folders
  1468. for (const FString& Path : FavoritePaths)
  1469. {
  1470. // by sending the whole path we deliberately include any children
  1471. // of successful hits in the filtered list.
  1472. if (SearchBoxFolderFilter->PassesFilter(Path))
  1473. {
  1474. TSharedPtr<FTreeItem> Item = AddPath(Path);
  1475. if (Item.IsValid())
  1476. {
  1477. const bool bSelectedItem = LastSelectedPaths.Contains(Item->FolderPath);
  1478. if (bSelectedItem)
  1479. {
  1480. // Tree items that match the last broadcasted paths should be re-selected them after they are added
  1481. TreeViewPtr->SetItemSelection(Item, true);
  1482. TreeViewPtr->RequestScrollIntoView(Item);
  1483. }
  1484. }
  1485. }
  1486. }
  1487. SortRootItems();
  1488. }
  1489. void SFavoritePathView::SaveSettings(const FString& IniFilename, const FString& IniSection, const FString& SettingsString) const
  1490. {
  1491. SExtPathView::SaveSettings(IniFilename, IniSection, SettingsString);
  1492. FString FavoritePathsString;
  1493. const TArray<FString> FavoritePaths = ExtContentBrowserUtils::GetFavoriteFolders();
  1494. for (const FString& PathIt : FavoritePaths)
  1495. {
  1496. if (FavoritePathsString.Len() > 0)
  1497. {
  1498. FavoritePathsString += TEXT(",");
  1499. }
  1500. FavoritePathsString += PathIt;
  1501. }
  1502. GConfig->SetString(*IniSection, TEXT("FavoritePaths"), *FavoritePathsString, IniFilename);
  1503. }
  1504. void SFavoritePathView::LoadSettings(const FString& IniFilename, const FString& IniSection, const FString& SettingsString)
  1505. {
  1506. SExtPathView::LoadSettings(IniFilename, IniSection, SettingsString);
  1507. // Selected Paths
  1508. FString SelectedPathsString;
  1509. TArray<FString> NewFavoritePaths;
  1510. if (GConfig->GetString(*IniSection, TEXT("FavoritePaths"), SelectedPathsString, IniFilename))
  1511. {
  1512. SelectedPathsString.ParseIntoArray(NewFavoritePaths, TEXT(","), /*bCullEmpty*/true);
  1513. }
  1514. if (NewFavoritePaths.Num() > 0)
  1515. {
  1516. // Keep track if we changed at least one source so we know to fire the bulk selection changed delegate later
  1517. bool bAddedAtLeastOnePath = false;
  1518. {
  1519. // If the selected paths is empty, the path was "All assets"
  1520. // This should handle that case properly
  1521. for (int32 PathIdx = 0; PathIdx < NewFavoritePaths.Num(); ++PathIdx)
  1522. {
  1523. const FString& Path = NewFavoritePaths[PathIdx];
  1524. ExtContentBrowserUtils::AddFavoriteFolder(Path, false);
  1525. bAddedAtLeastOnePath = true;
  1526. }
  1527. }
  1528. if (bAddedAtLeastOnePath)
  1529. {
  1530. Populate();
  1531. }
  1532. }
  1533. }
  1534. void SFavoritePathView::OnAssetTreeSearchBoxChanged(const FText& InSearchText)
  1535. {
  1536. SExtPathView::OnAssetTreeSearchBoxChanged(InSearchText);
  1537. OnFavoriteSearchChanged.ExecuteIfBound(InSearchText);
  1538. }
  1539. void SFavoritePathView::OnAssetTreeSearchBoxCommitted(const FText& InSearchText, ETextCommit::Type InCommitType)
  1540. {
  1541. SExtPathView::OnAssetTreeSearchBoxCommitted(InSearchText, InCommitType);
  1542. OnFavoriteSearchCommitted.ExecuteIfBound(InSearchText, InCommitType);
  1543. }
  1544. void SFavoritePathView::SetSelectedPaths(const TArray<FString>& Paths)
  1545. {
  1546. if (!ensure(TreeViewPtr.IsValid()))
  1547. {
  1548. return;
  1549. }
  1550. if (!SearchBoxPtr->GetText().IsEmpty())
  1551. {
  1552. // Clear the search box so the selected paths will be visible
  1553. SearchBoxPtr->SetText(FText::GetEmpty());
  1554. }
  1555. // Prevent the selection changed delegate since the invoking code requested it
  1556. FScopedPreventTreeItemChangedDelegate DelegatePrevention(SharedThis(this));
  1557. // If the selection was changed before all pending initial paths were found, stop attempting to select them
  1558. PendingInitialPaths.Empty();
  1559. // Clear the selection to start, then add the selected paths as they are found
  1560. TreeViewPtr->ClearSelection();
  1561. for (int32 PathIdx = 0; PathIdx < Paths.Num(); ++PathIdx)
  1562. {
  1563. const FString& Path = Paths[PathIdx];
  1564. TArray<FString> PathItemList;
  1565. Path.ParseIntoArray(PathItemList, TEXT("/"), /*InCullEmpty=*/true);
  1566. if (PathItemList.Num())
  1567. {
  1568. // There is at least one element in the path
  1569. TArray<TSharedPtr<FTreeItem>> TreeItems;
  1570. // Find the first item in the root items list
  1571. for (int32 RootItemIdx = 0; RootItemIdx < TreeRootItems.Num(); ++RootItemIdx)
  1572. {
  1573. if (TreeRootItems[RootItemIdx]->FolderName == PathItemList[0])
  1574. {
  1575. // Found the first item in the path
  1576. TreeItems.Add(TreeRootItems[RootItemIdx]);
  1577. break;
  1578. }
  1579. }
  1580. // If found in the root items list, try to find the childmost item matching the path
  1581. if (TreeItems.Num() > 0)
  1582. {
  1583. for (int32 PathItemIdx = 1; PathItemIdx < PathItemList.Num(); ++PathItemIdx)
  1584. {
  1585. const FString& PathItemName = PathItemList[PathItemIdx];
  1586. const TSharedPtr<FTreeItem> ChildItem = TreeItems.Last()->GetChild(PathItemName);
  1587. if (ChildItem.IsValid())
  1588. {
  1589. // Update tree items list
  1590. TreeItems.Add(ChildItem);
  1591. }
  1592. else
  1593. {
  1594. // Could not find the child item
  1595. break;
  1596. }
  1597. }
  1598. // Set the selection to the closest found folder and scroll it into view
  1599. TreeViewPtr->SetItemSelection(TreeItems.Last(), true);
  1600. TreeViewPtr->RequestScrollIntoView(TreeItems.Last());
  1601. }
  1602. else
  1603. {
  1604. // Could not even find the root path... skip
  1605. }
  1606. }
  1607. else
  1608. {
  1609. // No path items... skip
  1610. }
  1611. }
  1612. }
  1613. TSharedPtr<FTreeItem> SFavoritePathView::AddPath(const FString& InPath, const FString* RootPathPtr/* = nullptr*/, bool bUserNamed /*= false*/)
  1614. {
  1615. if (!ensure(TreeViewPtr.IsValid()))
  1616. {
  1617. // No tree view for some reason
  1618. return TSharedPtr<FTreeItem>();
  1619. }
  1620. FString Path = InPath;
  1621. FString RootPath;
  1622. if (RootPathPtr != nullptr)
  1623. {
  1624. RootPath = *RootPathPtr;
  1625. if (Path.Find(*RootPath) == 0)
  1626. {
  1627. Path = Path.Mid(FCString::Strlen(*RootPath));
  1628. }
  1629. }
  1630. TArray<FString> PathItemList;
  1631. Path.ParseIntoArray(PathItemList, TEXT("/"), /*InCullEmpty=*/true);
  1632. if (PathItemList.Num())
  1633. {
  1634. // There is at least one element in the path
  1635. const FString FolderName = PathItemList.Last();
  1636. const FString FolderPath = Path;
  1637. // Make sure the item is not already in the list
  1638. for (int32 RootItemIdx = 0; RootItemIdx < TreeRootItems.Num(); ++RootItemIdx)
  1639. {
  1640. if (TreeRootItems[RootItemIdx]->FolderPath == FolderPath)
  1641. {
  1642. // The root to add was already in the list return it here
  1643. return TreeRootItems[RootItemIdx];
  1644. }
  1645. }
  1646. TSharedPtr<struct FTreeItem> NewItem = nullptr;
  1647. // If this isn't an engine folder or we want to show them, add
  1648. const bool bDisplayEngine = GetDefault<UExtContentBrowserSettings>()->GetDisplayEngineFolder();
  1649. const bool bDisplayPlugins = GetDefault<UExtContentBrowserSettings>()->GetDisplayPluginFolders();
  1650. const bool bDisplayCpp = GetDefault<UExtContentBrowserSettings>()->GetDisplayCppFolders();
  1651. const bool bDisplayLoc = GetDefault<UExtContentBrowserSettings>()->GetDisplayL10NFolder();
  1652. // Filter out classes folders if we're not showing them.
  1653. if (!bDisplayCpp && ExtContentBrowserUtils::IsClassesFolder(FolderName))
  1654. {
  1655. return nullptr;
  1656. }
  1657. // Filter out plugin folders
  1658. bool bIsPlugin = false;
  1659. EPluginLoadedFrom PluginSource = EPluginLoadedFrom::Engine; // init to avoid warning
  1660. if (!bDisplayEngine || !bDisplayPlugins)
  1661. {
  1662. bIsPlugin = ExtContentBrowserUtils::IsPluginFolder(FolderName, &PluginSource);
  1663. }
  1664. if ((bDisplayEngine || !ExtContentBrowserUtils::IsEngineFolder(FolderName)) &&
  1665. ((bDisplayEngine && bDisplayPlugins) || !(bIsPlugin && PluginSource == EPluginLoadedFrom::Engine)) &&
  1666. (bDisplayPlugins || !(bIsPlugin && PluginSource == EPluginLoadedFrom::Project)) &&
  1667. (bDisplayLoc || !ExtContentBrowserUtils::IsLocalizationFolder(FolderName)))
  1668. {
  1669. const FText DisplayName = ExtContentBrowserUtils::GetRootDirDisplayName(FolderName);
  1670. NewItem = MakeShareable(new FTreeItem(FText::FromString(FolderName), FolderName, FolderPath, RootPath, TSharedPtr<FTreeItem>()));
  1671. TreeRootItems.Add(NewItem);
  1672. TreeViewPtr->RequestTreeRefresh();
  1673. TreeViewPtr->SetSelection(NewItem);
  1674. }
  1675. return NewItem;
  1676. }
  1677. return TSharedPtr<FTreeItem>();
  1678. }
  1679. TSharedRef<ITableRow> SFavoritePathView::GenerateTreeRow(TSharedPtr<FTreeItem> TreeItem, const TSharedRef<STableViewBase>& OwnerTable)
  1680. {
  1681. check(TreeItem.IsValid());
  1682. return
  1683. SNew( STableRow< TSharedPtr<FTreeItem> >, OwnerTable )
  1684. .OnDragDetected( this, &SFavoritePathView::OnFolderDragDetected )
  1685. [
  1686. SNew(SExtAssetTreeItem)
  1687. .TreeItem(TreeItem)
  1688. .OnAssetsOrPathsDragDropped(this, &SFavoritePathView::TreeAssetsOrPathsDropped)
  1689. .OnFilesDragDropped(this, &SFavoritePathView::TreeFilesDropped)
  1690. .IsItemExpanded(false)
  1691. .HighlightText(this, &SFavoritePathView::GetHighlightText)
  1692. .IsSelected(this, &SFavoritePathView::IsTreeItemSelected, TreeItem)
  1693. .FontOverride(FAppStyle::GetFontStyle("ContentBrowser.SourceTreeItemFont"))
  1694. ];
  1695. }
  1696. void SFavoritePathView::OnAssetRegistryPathAdded(const FString& Path)
  1697. {
  1698. }
  1699. void SFavoritePathView::OnAssetRegistryPathRemoved(const FString& Path)
  1700. {
  1701. ExtContentBrowserUtils::RemoveFavoriteFolder(Path);
  1702. Populate();
  1703. }
  1704. void SFavoritePathView::ExecuteTreeDropMove(TArray<FAssetData> AssetList, TArray<FString> AssetPaths, FString DestinationPath)
  1705. {
  1706. #if ECB_LEGACY
  1707. if (AssetList.Num() > 0)
  1708. {
  1709. TArray<UObject*> DroppedObjects;
  1710. ContentBrowserUtils::GetObjectsInAssetData(AssetList, DroppedObjects);
  1711. ContentBrowserUtils::MoveAssets(DroppedObjects, DestinationPath);
  1712. }
  1713. // Prepare to fixup any asset paths that are favorites
  1714. TArray<FMovedContentFolder> MovedFolders;
  1715. for (const FString& OldPath : AssetPaths)
  1716. {
  1717. const FString SubFolderName = FPackageName::GetLongPackageAssetName(OldPath);
  1718. const FString NewPath = DestinationPath + TEXT("/") + SubFolderName;
  1719. MovedFolders.Add(FMovedContentFolder(OldPath, NewPath));
  1720. }
  1721. FixupFavoritesFromExternalChange(MovedFolders);
  1722. ContentBrowserUtils::MoveFolders(AssetPaths, DestinationPath);
  1723. #endif
  1724. }
  1725. FText SExtPathView::GetTreeTitle() const
  1726. {
  1727. const UExtContentBrowserSettings* ExtContentBrowserSettings = GetDefault<UExtContentBrowserSettings>();
  1728. const bool bCacheMode = ExtContentBrowserSettings->bCacheMode;
  1729. if (bCacheMode)
  1730. {
  1731. const FString& CacheFilePath = ExtContentBrowserSettings->CacheFilePath.FilePath;
  1732. const FString FileName = FPaths::GetBaseFilename(CacheFilePath);
  1733. return FText::Format(LOCTEXT("CacheModeTreeTitle", "{0}"), FText::FromString(FileName));
  1734. }
  1735. else
  1736. {
  1737. return TreeTitle;
  1738. }
  1739. }
  1740. EVisibility SExtPathView::GetTreeTitleVisibility() const
  1741. {
  1742. const UExtContentBrowserSettings* ExtContentBrowserSettings = GetDefault<UExtContentBrowserSettings>();
  1743. const bool bCacheMode = ExtContentBrowserSettings->bCacheMode;
  1744. if (bCacheMode)
  1745. {
  1746. return EVisibility::Visible;
  1747. }
  1748. else
  1749. {
  1750. return EVisibility::Collapsed;
  1751. }
  1752. }
  1753. #undef LOCTEXT_NAMESPACE