ExtContentBrowserUtils.cpp 73 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252
  1. // Copyright 2017-2021 marynate. All Rights Reserved.
  2. #include "ExtContentBrowserUtils.h"
  3. #include "ExtContentBrowserSingleton.h"
  4. #include "SExtAssetView.h"
  5. #include "SExtPathView.h"
  6. #include "HAL/IConsoleManager.h"
  7. #include "Misc/MessageDialog.h"
  8. #include "HAL/FileManager.h"
  9. #include "HAL/PlatformApplicationMisc.h"
  10. #include "Misc/Paths.h"
  11. #include "Misc/ConfigCacheIni.h"
  12. #include "Misc/FeedbackContext.h"
  13. #include "Misc/ScopedSlowTask.h"
  14. #include "Misc/App.h"
  15. #include "Misc/FileHelper.h"
  16. #include "Framework/Application/MenuStack.h"
  17. #include "Framework/Application/SlateApplication.h"
  18. #include "Framework/Notifications/NotificationManager.h"
  19. #include "Widgets/Notifications/SNotificationList.h"
  20. #include "Modules/ModuleManager.h"
  21. #include "Widgets/DeclarativeSyntaxSupport.h"
  22. #include "Widgets/SCompoundWidget.h"
  23. #include "Widgets/SBoxPanel.h"
  24. #include "Widgets/Layout/SBorder.h"
  25. #include "Widgets/Images/SImage.h"
  26. #include "Widgets/Text/STextBlock.h"
  27. #include "Widgets/Layout/SUniformGridPanel.h"
  28. #include "Widgets/Input/SButton.h"
  29. #include "Layout/WidgetPath.h"
  30. #include "SlateOptMacros.h"
  31. #include "EditorStyleSet.h"
  32. #include "UnrealClient.h"
  33. #include "Engine/World.h"
  34. #include "Settings/EditorExperimentalSettings.h"
  35. #include "FileHelpers.h"
  36. #include "AssetRegistry/ARFilter.h"
  37. #include "AssetRegistry/AssetRegistryModule.h"
  38. #include "IAssetTools.h"
  39. #include "AssetToolsModule.h"
  40. #include "Subsystems/AssetEditorSubsystem.h"
  41. #include "PackagesDialog.h"
  42. #include "PackageTools.h"
  43. #include "ObjectTools.h"
  44. #include "ImageUtils.h"
  45. #include "Logging/MessageLog.h"
  46. #include "Misc/EngineBuildSettings.h"
  47. #include "Interfaces/IPluginManager.h"
  48. #define LOCTEXT_NAMESPACE "ExtContentBrowser"
  49. #define MAX_CLASS_NAME_LENGTH 32 // Enforce a reasonable class name length so the path is not too long for FPlatformMisc::GetMaxPathLength()
  50. namespace ExtContentBrowserUtils
  51. {
  52. // Keep a map of all the paths that have custom colors, so updating the color in one location updates them all
  53. static TMap< FString, TSharedPtr< FLinearColor > > PathColors;
  54. /** Internal function to delete a folder from disk, but only if it is empty. InPathToDelete is in FPackageName format. */
  55. bool DeleteEmptyFolderFromDisk(const FString& InPathToDelete);
  56. }
  57. class SExtContentBrowserPopup : public SCompoundWidget
  58. {
  59. public:
  60. SLATE_BEGIN_ARGS( SExtContentBrowserPopup ){}
  61. SLATE_ATTRIBUTE( FText, Message )
  62. SLATE_END_ARGS()
  63. /** Constructs this widget with InArgs */
  64. BEGIN_SLATE_FUNCTION_BUILD_OPTIMIZATION
  65. void Construct( const FArguments& InArgs )
  66. {
  67. ChildSlot
  68. [
  69. SNew(SBorder)
  70. .BorderImage(FAppStyle::GetBrush("Menu.Background"))
  71. .Padding(10)
  72. .OnMouseButtonDown(this, &SExtContentBrowserPopup::OnBorderClicked)
  73. .BorderBackgroundColor(this, &SExtContentBrowserPopup::GetBorderBackgroundColor)
  74. [
  75. SNew(SHorizontalBox)
  76. +SHorizontalBox::Slot()
  77. .AutoWidth()
  78. .VAlign(VAlign_Center)
  79. .Padding(0, 0, 4, 0)
  80. [
  81. SNew(SImage) .Image( FAppStyle::GetBrush("ContentBrowser.PopupMessageIcon") )
  82. ]
  83. +SHorizontalBox::Slot()
  84. .AutoWidth()
  85. .VAlign(VAlign_Center)
  86. [
  87. SNew(STextBlock)
  88. .Text(InArgs._Message)
  89. .WrapTextAt(450)
  90. ]
  91. ]
  92. ];
  93. }
  94. END_SLATE_FUNCTION_BUILD_OPTIMIZATION
  95. static void DisplayMessage( const FText& Message, const FSlateRect& ScreenAnchor, TSharedRef<SWidget> ParentContent )
  96. {
  97. TSharedRef<SExtContentBrowserPopup> PopupContent = SNew(SExtContentBrowserPopup) .Message(Message);
  98. const FVector2D ScreenLocation = FVector2D(ScreenAnchor.Left, ScreenAnchor.Top);
  99. const bool bFocusImmediately = true;
  100. const FVector2D SummonLocationSize = ScreenAnchor.GetSize();
  101. TSharedPtr<IMenu> Menu = FSlateApplication::Get().PushMenu(
  102. ParentContent,
  103. FWidgetPath(),
  104. PopupContent,
  105. ScreenLocation,
  106. FPopupTransitionEffect( FPopupTransitionEffect::TopMenu ),
  107. bFocusImmediately,
  108. SummonLocationSize);
  109. PopupContent->SetMenu(Menu);
  110. }
  111. private:
  112. void SetMenu(const TSharedPtr<IMenu>& InMenu)
  113. {
  114. Menu = InMenu;
  115. }
  116. FReply OnBorderClicked(const FGeometry& Geometry, const FPointerEvent& MouseEvent)
  117. {
  118. if (Menu.IsValid())
  119. {
  120. Menu.Pin()->Dismiss();
  121. }
  122. return FReply::Handled();
  123. }
  124. FSlateColor GetBorderBackgroundColor() const
  125. {
  126. return IsHovered() ? FLinearColor(0.5, 0.5, 0.5, 1) : FLinearColor::White;
  127. }
  128. private:
  129. TWeakPtr<IMenu> Menu;
  130. };
  131. /** A miniture confirmation popup for quick yes/no questions */
  132. class SExtContentBrowserConfirmPopup : public SCompoundWidget
  133. {
  134. public:
  135. SLATE_BEGIN_ARGS( SExtContentBrowserConfirmPopup ) {}
  136. /** The text to display */
  137. SLATE_ARGUMENT(FText, Prompt)
  138. /** The Yes Button to display */
  139. SLATE_ARGUMENT(FText, YesText)
  140. /** The No Button to display */
  141. SLATE_ARGUMENT(FText, NoText)
  142. /** Invoked when yes is clicked */
  143. SLATE_EVENT(FOnClicked, OnYesClicked)
  144. /** Invoked when no is clicked */
  145. SLATE_EVENT(FOnClicked, OnNoClicked)
  146. SLATE_END_ARGS()
  147. BEGIN_SLATE_FUNCTION_BUILD_OPTIMIZATION
  148. void Construct( const FArguments& InArgs )
  149. {
  150. OnYesClicked = InArgs._OnYesClicked;
  151. OnNoClicked = InArgs._OnNoClicked;
  152. ChildSlot
  153. [
  154. SNew(SBorder)
  155. . BorderImage(FAppStyle::GetBrush("Menu.Background"))
  156. . Padding(10)
  157. [
  158. SNew(SVerticalBox)
  159. +SVerticalBox::Slot()
  160. .AutoHeight()
  161. .Padding(0, 0, 0, 5)
  162. .HAlign(HAlign_Center)
  163. [
  164. SNew(STextBlock)
  165. .Text(InArgs._Prompt)
  166. ]
  167. +SVerticalBox::Slot()
  168. .AutoHeight()
  169. .HAlign(HAlign_Center)
  170. [
  171. SNew(SUniformGridPanel)
  172. .SlotPadding(3)
  173. + SUniformGridPanel::Slot(0, 0)
  174. .HAlign(HAlign_Fill)
  175. [
  176. SNew(SButton)
  177. .HAlign(HAlign_Center)
  178. .Text(InArgs._YesText)
  179. .OnClicked( this, &SExtContentBrowserConfirmPopup::YesClicked )
  180. ]
  181. + SUniformGridPanel::Slot(1, 0)
  182. .HAlign(HAlign_Fill)
  183. [
  184. SNew(SButton)
  185. .HAlign(HAlign_Center)
  186. .Text(InArgs._NoText)
  187. .OnClicked( this, &SExtContentBrowserConfirmPopup::NoClicked )
  188. ]
  189. ]
  190. ]
  191. ];
  192. }
  193. END_SLATE_FUNCTION_BUILD_OPTIMIZATION
  194. /** Opens the popup using the specified component as its parent */
  195. void OpenPopup(const TSharedRef<SWidget>& ParentContent)
  196. {
  197. // Show dialog to confirm the delete
  198. Menu = FSlateApplication::Get().PushMenu(
  199. ParentContent,
  200. FWidgetPath(),
  201. SharedThis(this),
  202. FSlateApplication::Get().GetCursorPos(),
  203. FPopupTransitionEffect( FPopupTransitionEffect::TopMenu )
  204. );
  205. }
  206. private:
  207. /** The yes button was clicked */
  208. FReply YesClicked()
  209. {
  210. if ( OnYesClicked.IsBound() )
  211. {
  212. OnYesClicked.Execute();
  213. }
  214. if (Menu.IsValid())
  215. {
  216. Menu.Pin()->Dismiss();
  217. }
  218. return FReply::Handled();
  219. }
  220. /** The no button was clicked */
  221. FReply NoClicked()
  222. {
  223. if ( OnNoClicked.IsBound() )
  224. {
  225. OnNoClicked.Execute();
  226. }
  227. if (Menu.IsValid())
  228. {
  229. Menu.Pin()->Dismiss();
  230. }
  231. return FReply::Handled();
  232. }
  233. /** The IMenu prepresenting this popup */
  234. TWeakPtr<IMenu> Menu;
  235. /** Delegates for button clicks */
  236. FOnClicked OnYesClicked;
  237. FOnClicked OnNoClicked;
  238. };
  239. bool ExtContentBrowserUtils::OpenEditorForAsset(const FString& ObjectPath)
  240. {
  241. // Load the asset if unloaded
  242. TArray<UObject*> LoadedObjects;
  243. TArray<FString> ObjectPaths;
  244. ObjectPaths.Add(ObjectPath);
  245. ExtContentBrowserUtils::LoadAssetsIfNeeded(ObjectPaths, LoadedObjects);
  246. // Open the editor for the specified asset
  247. UObject* FoundObject = FindObject<UObject>(NULL, *ObjectPath);
  248. return OpenEditorForAsset(FoundObject);
  249. }
  250. bool ExtContentBrowserUtils::OpenEditorForAsset(UObject* Asset)
  251. {
  252. if( Asset != NULL )
  253. {
  254. // @todo toolkit minor: Needs world-centric support?
  255. return GEditor->GetEditorSubsystem<UAssetEditorSubsystem>()->OpenEditorForAsset(Asset);
  256. }
  257. return false;
  258. }
  259. bool ExtContentBrowserUtils::OpenEditorForAsset(const TArray<UObject*>& Assets)
  260. {
  261. if ( Assets.Num() == 1 )
  262. {
  263. return OpenEditorForAsset(Assets[0]);
  264. }
  265. else if ( Assets.Num() > 1 )
  266. {
  267. return GEditor->GetEditorSubsystem<UAssetEditorSubsystem>()->OpenEditorForAssets(Assets);
  268. }
  269. return false;
  270. }
  271. bool ExtContentBrowserUtils::LoadAssetsIfNeeded(const TArray<FString>& ObjectPaths, TArray<UObject*>& LoadedObjects, bool bAllowedToPromptToLoadAssets, bool bLoadRedirects)
  272. {
  273. bool bAnyObjectsWereLoadedOrUpdated = false;
  274. // Build a list of unloaded assets
  275. TArray<FString> UnloadedObjectPaths;
  276. bool bAtLeastOneUnloadedMap = false;
  277. for (int32 PathIdx = 0; PathIdx < ObjectPaths.Num(); ++PathIdx)
  278. {
  279. const FString& ObjectPath = ObjectPaths[PathIdx];
  280. UObject* FoundObject = FindObject<UObject>(NULL, *ObjectPath);
  281. if ( FoundObject )
  282. {
  283. LoadedObjects.Add(FoundObject);
  284. }
  285. else
  286. {
  287. // Unloaded asset, we will load it later
  288. UnloadedObjectPaths.Add(ObjectPath);
  289. if ( FEditorFileUtils::IsMapPackageAsset(ObjectPath) )
  290. {
  291. bAtLeastOneUnloadedMap = true;
  292. }
  293. }
  294. }
  295. // Make sure all selected objects are loaded, where possible
  296. if ( UnloadedObjectPaths.Num() > 0 )
  297. {
  298. // Get the maximum objects to load before displaying the slow task
  299. const bool bShowProgressDialog = (UnloadedObjectPaths.Num() > GetDefault<UExtContentBrowserSettings>()->NumObjectsToLoadBeforeWarning) || bAtLeastOneUnloadedMap;
  300. FScopedSlowTask SlowTask(UnloadedObjectPaths.Num(), LOCTEXT("LoadingObjects", "Loading Objects..."));
  301. if (bShowProgressDialog)
  302. {
  303. SlowTask.MakeDialog();
  304. }
  305. GIsEditorLoadingPackage = true;
  306. // We usually don't want to follow redirects when loading objects for the Content Browser. It would
  307. // allow a user to interact with a ghost/unverified asset as if it were still alive.
  308. // This can be overridden by providing bLoadRedirects = true as a parameter.
  309. const ELoadFlags LoadFlags = bLoadRedirects ? LOAD_None : LOAD_NoRedirects;
  310. bool bSomeObjectsFailedToLoad = false;
  311. for (int32 PathIdx = 0; PathIdx < UnloadedObjectPaths.Num(); ++PathIdx)
  312. {
  313. const FString& ObjectPath = UnloadedObjectPaths[PathIdx];
  314. SlowTask.EnterProgressFrame(1, FText::Format(LOCTEXT("LoadingObjectf", "Loading {0}..."), FText::FromString(ObjectPath)));
  315. // Load up the object
  316. UObject* LoadedObject = LoadObject<UObject>(NULL, *ObjectPath, NULL, LoadFlags, NULL);
  317. if ( LoadedObject )
  318. {
  319. LoadedObjects.Add(LoadedObject);
  320. }
  321. else
  322. {
  323. bSomeObjectsFailedToLoad = true;
  324. }
  325. if (GWarn->ReceivedUserCancel())
  326. {
  327. // If the user has canceled stop loading the remaining objects. We don't add the remaining objects to the failed string,
  328. // this would only result in launching another dialog when by their actions the user clearly knows not all of the
  329. // assets will have been loaded.
  330. break;
  331. }
  332. }
  333. GIsEditorLoadingPackage = false;
  334. if ( bSomeObjectsFailedToLoad )
  335. {
  336. FNotificationInfo Info(LOCTEXT("LoadObjectFailed", "Failed to load assets"));
  337. Info.ExpireDuration = 5.0f;
  338. Info.Hyperlink = FSimpleDelegate::CreateStatic([](){ FMessageLog("LoadErrors").Open(EMessageSeverity::Info, true); });
  339. Info.HyperlinkText = LOCTEXT("LoadObjectHyperlink", "Show Message Log");
  340. FSlateNotificationManager::Get().AddNotification(Info);
  341. return false;
  342. }
  343. }
  344. return true;
  345. }
  346. void ExtContentBrowserUtils::GetUnloadedAssets(const TArray<FString>& ObjectPaths, TArray<FString>& OutUnloadedObjects)
  347. {
  348. OutUnloadedObjects.Empty();
  349. // Build a list of unloaded assets and check if there are any parent folders
  350. for (int32 PathIdx = 0; PathIdx < ObjectPaths.Num(); ++PathIdx)
  351. {
  352. const FString& ObjectPath = ObjectPaths[PathIdx];
  353. UObject* FoundObject = FindObject<UObject>(NULL, *ObjectPath);
  354. if ( !FoundObject )
  355. {
  356. // Unloaded asset, we will load it later
  357. OutUnloadedObjects.Add(ObjectPath);
  358. }
  359. }
  360. }
  361. bool ExtContentBrowserUtils::PromptToLoadAssets(const TArray<FString>& UnloadedObjects)
  362. {
  363. bool bShouldLoadAssets = false;
  364. // Prompt the user to load assets
  365. const FText Question = FText::Format( LOCTEXT("ConfirmLoadAssets", "You are about to load {0} assets. Would you like to proceed?"), FText::AsNumber( UnloadedObjects.Num() ) );
  366. if ( EAppReturnType::Yes == FMessageDialog::Open(EAppMsgType::YesNo, Question) )
  367. {
  368. bShouldLoadAssets = true;
  369. }
  370. return bShouldLoadAssets;
  371. }
  372. bool ExtContentBrowserUtils::CanRenameFolder(const FString& InFolderPath)
  373. {
  374. // Cannot rename folders that are part of a classes or collections root
  375. return !ExtContentBrowserUtils::IsClassPath(InFolderPath) && !ExtContentBrowserUtils::IsCollectionPath(InFolderPath);
  376. }
  377. bool ExtContentBrowserUtils::CanRenameAsset(const FAssetData& InAssetData)
  378. {
  379. // Cannot rename redirectors or classes or cooked packages
  380. return !InAssetData.IsRedirector() && InAssetData.AssetClassPath != FTopLevelAssetPath(TEXT("/Script/CoreUObject"), NAME_Class) && !(InAssetData.PackageFlags & PKG_FilterEditorOnly);
  381. }
  382. void ExtContentBrowserUtils::RenameAsset(UObject* Asset, const FString& NewName, FText& ErrorMessage)
  383. {
  384. FAssetToolsModule& AssetToolsModule = FModuleManager::LoadModuleChecked<FAssetToolsModule>("AssetTools");
  385. TArray<FAssetRenameData> AssetsAndNames;
  386. const FString PackagePath = FPackageName::GetLongPackagePath(Asset->GetOutermost()->GetName());
  387. new(AssetsAndNames) FAssetRenameData(Asset, PackagePath, NewName);
  388. AssetToolsModule.Get().RenameAssetsWithDialog(AssetsAndNames);
  389. }
  390. void ExtContentBrowserUtils::CopyAssets(const TArray<UObject*>& Assets, const FString& DestPath)
  391. {
  392. #if ECB_LEGACY
  393. TArray<UObject*> NewObjects;
  394. ObjectTools::DuplicateObjects(Assets, TEXT(""), DestPath, /*bOpenDialog=*/false, &NewObjects);
  395. // If any objects were duplicated, report the success
  396. if ( NewObjects.Num() )
  397. {
  398. FFormatNamedArguments Args;
  399. Args.Add( TEXT("Number"), NewObjects.Num() );
  400. const FText Message = FText::Format( LOCTEXT("AssetsDroppedCopy", "{Number} asset(s) copied"), Args );
  401. FSlateNotificationManager::Get().AddNotification(FNotificationInfo(Message));
  402. // Now branch the files in source control if possible
  403. check(Assets.Num() == NewObjects.Num());
  404. for(int32 ObjectIndex = 0; ObjectIndex < Assets.Num(); ObjectIndex++)
  405. {
  406. UObject* SourceAsset = Assets[ObjectIndex];
  407. UObject* DestAsset = NewObjects[ObjectIndex];
  408. SourceControlHelpers::BranchPackage(DestAsset->GetOutermost(), SourceAsset->GetOutermost());
  409. }
  410. }
  411. #endif
  412. }
  413. void ExtContentBrowserUtils::MoveAssets(const TArray<UObject*>& Assets, const FString& DestPath, const FString& SourcePath)
  414. {
  415. check(DestPath.Len() > 0);
  416. FAssetToolsModule& AssetToolsModule = FModuleManager::LoadModuleChecked<FAssetToolsModule>("AssetTools");
  417. TArray<FAssetRenameData> AssetsAndNames;
  418. for ( auto AssetIt = Assets.CreateConstIterator(); AssetIt; ++AssetIt )
  419. {
  420. UObject* Asset = *AssetIt;
  421. if ( !ensure(Asset) )
  422. {
  423. continue;
  424. }
  425. FString PackagePath;
  426. FString ObjectName = Asset->GetName();
  427. if ( SourcePath.Len() )
  428. {
  429. const FString CurrentPackageName = Asset->GetOutermost()->GetName();
  430. // This is a relative operation
  431. if ( !ensure(CurrentPackageName.StartsWith(SourcePath)) )
  432. {
  433. continue;
  434. }
  435. // Collect the relative path then use it to determine the new location
  436. // For example, if SourcePath = /Game/MyPath and CurrentPackageName = /Game/MyPath/MySubPath/MyAsset
  437. // /Game/MyPath/MySubPath/MyAsset -> /MySubPath
  438. const int32 ShortPackageNameLen = FPackageName::GetLongPackageAssetName(CurrentPackageName).Len();
  439. const int32 RelativePathLen = CurrentPackageName.Len() - ShortPackageNameLen - SourcePath.Len() - 1; // -1 to exclude the trailing "/"
  440. const FString RelativeDestPath = CurrentPackageName.Mid(SourcePath.Len(), RelativePathLen);
  441. PackagePath = DestPath + RelativeDestPath;
  442. }
  443. else
  444. {
  445. // Only a DestPath was supplied, use it
  446. PackagePath = DestPath;
  447. }
  448. new(AssetsAndNames) FAssetRenameData(Asset, PackagePath, ObjectName);
  449. }
  450. if ( AssetsAndNames.Num() > 0 )
  451. {
  452. AssetToolsModule.Get().RenameAssetsWithDialog(AssetsAndNames);
  453. }
  454. }
  455. int32 ExtContentBrowserUtils::DeleteAssets(const TArray<UObject*>& AssetsToDelete)
  456. {
  457. return ObjectTools::DeleteObjects(AssetsToDelete);
  458. }
  459. bool ExtContentBrowserUtils::DeleteFolders(const TArray<FString>& PathsToDelete)
  460. {
  461. // Get a list of assets in the paths to delete
  462. TArray<FExtAssetData> AssetDataList;
  463. GetAssetsInPaths(PathsToDelete, AssetDataList);
  464. const int32 NumAssetsInPaths = AssetDataList.Num();
  465. bool bAllowFolderDelete = false;
  466. if ( NumAssetsInPaths == 0 )
  467. {
  468. // There were no assets, allow the folder delete.
  469. bAllowFolderDelete = true;
  470. }
  471. else
  472. {
  473. // Load all the assets in the folder and attempt to delete them.
  474. // If it was successful, allow the folder delete.
  475. // Get a list of object paths for input into LoadAssetsIfNeeded
  476. TArray<FString> ObjectPaths;
  477. for ( auto AssetIt = AssetDataList.CreateConstIterator(); AssetIt; ++AssetIt )
  478. {
  479. ObjectPaths.Add((*AssetIt).ObjectPath.ToString());
  480. }
  481. // Load all the assets in the selected paths
  482. TArray<UObject*> LoadedAssets;
  483. if ( ExtContentBrowserUtils::LoadAssetsIfNeeded(ObjectPaths, LoadedAssets) )
  484. {
  485. // Make sure we loaded all of them
  486. if ( LoadedAssets.Num() == NumAssetsInPaths )
  487. {
  488. TArray<UObject*> ToDelete = LoadedAssets;
  489. ObjectTools::AddExtraObjectsToDelete(ToDelete);
  490. const int32 NumAssetsDeleted = ExtContentBrowserUtils::DeleteAssets(ToDelete);
  491. if ( NumAssetsDeleted == ToDelete.Num() )
  492. {
  493. // Successfully deleted all assets in the specified path. Allow the folder to be removed.
  494. bAllowFolderDelete = true;
  495. }
  496. else
  497. {
  498. // Not all the assets in the selected paths were deleted
  499. }
  500. }
  501. else
  502. {
  503. // Not all the assets in the selected paths were loaded
  504. }
  505. }
  506. else
  507. {
  508. // The user declined to load some assets or some assets failed to load
  509. }
  510. }
  511. if ( bAllowFolderDelete )
  512. {
  513. FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked<FAssetRegistryModule>("AssetRegistry");
  514. for (const FString& PathToDelete : PathsToDelete)
  515. {
  516. if (DeleteEmptyFolderFromDisk(PathToDelete))
  517. {
  518. AssetRegistryModule.Get().RemovePath(PathToDelete);
  519. }
  520. }
  521. return true;
  522. }
  523. return false;
  524. }
  525. bool ExtContentBrowserUtils::DeleteEmptyFolderFromDisk(const FString& InPathToDelete)
  526. {
  527. struct FEmptyFolderVisitor : public IPlatformFile::FDirectoryVisitor
  528. {
  529. bool bIsEmpty;
  530. FEmptyFolderVisitor()
  531. : bIsEmpty(true)
  532. {
  533. }
  534. virtual bool Visit(const TCHAR* FilenameOrDirectory, bool bIsDirectory) override
  535. {
  536. if (!bIsDirectory)
  537. {
  538. bIsEmpty = false;
  539. return false; // abort searching
  540. }
  541. return true; // continue searching
  542. }
  543. };
  544. FString PathToDeleteOnDisk;
  545. if (FPackageName::TryConvertLongPackageNameToFilename(InPathToDelete, PathToDeleteOnDisk))
  546. {
  547. // Look for files on disk in case the folder contains things not tracked by the asset registry
  548. FEmptyFolderVisitor EmptyFolderVisitor;
  549. IFileManager::Get().IterateDirectoryRecursively(*PathToDeleteOnDisk, EmptyFolderVisitor);
  550. if (EmptyFolderVisitor.bIsEmpty)
  551. {
  552. return IFileManager::Get().DeleteDirectory(*PathToDeleteOnDisk, false, true);
  553. }
  554. }
  555. return false;
  556. }
  557. void ExtContentBrowserUtils::GetAssetsInPaths(const TArray<FString>& InPaths, TArray<FExtAssetData>& OutAssetDataList)
  558. {
  559. // Load the asset registry module
  560. FAssetRegistryModule& AssetRegistryModule = FModuleManager::Get().LoadModuleChecked<FAssetRegistryModule>(TEXT("AssetRegistry"));
  561. // Form a filter from the paths
  562. FARFilter Filter;
  563. Filter.bRecursivePaths = true;
  564. for (int32 PathIdx = 0; PathIdx < InPaths.Num(); ++PathIdx)
  565. {
  566. new (Filter.PackagePaths) FName(*InPaths[PathIdx]);
  567. }
  568. // Query for a list of assets in the selected paths
  569. FExtContentBrowserSingleton::GetAssetRegistry().GetAssets(Filter, OutAssetDataList);
  570. }
  571. bool ExtContentBrowserUtils::SavePackages(const TArray<UPackage*>& Packages)
  572. {
  573. const bool bCheckDirty = false;
  574. const bool bPromptToSave = false;
  575. const FEditorFileUtils::EPromptReturnCode Return = FEditorFileUtils::PromptForCheckoutAndSave(Packages, bCheckDirty, bPromptToSave);
  576. return Return == FEditorFileUtils::EPromptReturnCode::PR_Success;
  577. }
  578. bool ExtContentBrowserUtils::SaveDirtyPackages()
  579. {
  580. const bool bPromptUserToSave = true;
  581. const bool bSaveMapPackages = true;
  582. const bool bSaveContentPackages = true;
  583. const bool bFastSave = false;
  584. const bool bNotifyNoPackagesSaved = false;
  585. const bool bCanBeDeclined = false;
  586. return FEditorFileUtils::SaveDirtyPackages( bPromptUserToSave, bSaveMapPackages, bSaveContentPackages, bFastSave, bNotifyNoPackagesSaved, bCanBeDeclined );
  587. }
  588. TArray<UPackage*> ExtContentBrowserUtils::LoadPackages(const TArray<FString>& PackageNames)
  589. {
  590. TArray<UPackage*> LoadedPackages;
  591. GWarn->BeginSlowTask( LOCTEXT("LoadingPackages", "Loading Packages..."), true );
  592. for (int32 PackageIdx = 0; PackageIdx < PackageNames.Num(); ++PackageIdx)
  593. {
  594. const FString& PackageName = PackageNames[PackageIdx];
  595. if ( !ensure(PackageName.Len() > 0) )
  596. {
  597. // Empty package name. Skip it.
  598. continue;
  599. }
  600. UPackage* Package = FindPackage(NULL, *PackageName);
  601. if ( Package != NULL )
  602. {
  603. // The package is at least partially loaded. Fully load it.
  604. Package->FullyLoad();
  605. }
  606. else
  607. {
  608. // The package is unloaded. Try to load the package from disk.
  609. Package = UPackageTools::LoadPackage(PackageName);
  610. }
  611. // If the package was loaded, add it to the loaded packages list.
  612. if ( Package != NULL )
  613. {
  614. LoadedPackages.Add(Package);
  615. }
  616. }
  617. GWarn->EndSlowTask();
  618. return LoadedPackages;
  619. }
  620. void ExtContentBrowserUtils::NotifyMessage(const FText& Message, bool bAlsoPrintToConsole, float InDuration /*= 1.0f*/)
  621. {
  622. if (!Message.IsEmpty())
  623. {
  624. FNotificationInfo NotificationInfo(Message);
  625. NotificationInfo.ExpireDuration = InDuration;
  626. FSlateNotificationManager::Get().AddNotification(NotificationInfo);
  627. if (bAlsoPrintToConsole)
  628. {
  629. ECB_INFO(Display, TEXT("%s"), *Message.ToString());
  630. }
  631. }
  632. }
  633. void ExtContentBrowserUtils::NotifyMessage(const FString& Message, float InDuration /*= 3.f*/)
  634. {
  635. NotifyMessage(FText::FromString(Message), false, InDuration);
  636. }
  637. void ExtContentBrowserUtils::DisplayMessage(const FText& Message, const FSlateRect& ScreenAnchor, const TSharedRef<SWidget>& ParentContent)
  638. {
  639. SExtContentBrowserPopup::DisplayMessage(Message, ScreenAnchor, ParentContent);
  640. }
  641. void ExtContentBrowserUtils::DisplayMessagePopup(const FText& Message)
  642. {
  643. FMessageDialog::Open(EAppMsgType::Ok, Message);
  644. }
  645. void ExtContentBrowserUtils::DisplayConfirmationPopup(const FText& Message, const FText& YesString, const FText& NoString, const TSharedRef<SWidget>& ParentContent, const FOnClicked& OnYesClicked, const FOnClicked& OnNoClicked)
  646. {
  647. TSharedRef<SExtContentBrowserConfirmPopup> Popup =
  648. SNew(SExtContentBrowserConfirmPopup)
  649. .Prompt(Message)
  650. .YesText(YesString)
  651. .NoText(NoString)
  652. .OnYesClicked( OnYesClicked )
  653. .OnNoClicked( OnNoClicked );
  654. Popup->OpenPopup(ParentContent);
  655. }
  656. #if ECB_LEGACY
  657. bool ExtContentBrowserUtils::RenameFolder(const FString& DestPath, const FString& SourcePath)
  658. {
  659. if (DestPath == SourcePath)
  660. {
  661. return false;
  662. }
  663. FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked<FAssetRegistryModule>("AssetRegistry");
  664. // move any assets in our folder
  665. TArray<FAssetData> AssetsInFolder;
  666. AssetRegistryModule.Get().GetAssetsByPath(*SourcePath, AssetsInFolder, true);
  667. TArray<UObject*> ObjectsInFolder;
  668. GetObjectsInAssetData(AssetsInFolder, ObjectsInFolder);
  669. MoveAssets(ObjectsInFolder, DestPath, SourcePath);
  670. // Now check to see if the original folder is empty, if so we can delete it
  671. TArray<FAssetData> AssetsInOriginalFolder;
  672. AssetRegistryModule.Get().GetAssetsByPath(*SourcePath, AssetsInOriginalFolder, true);
  673. if (AssetsInOriginalFolder.Num() == 0)
  674. {
  675. TArray<FString> FoldersToDelete;
  676. FoldersToDelete.Add(SourcePath);
  677. DeleteFolders(FoldersToDelete);
  678. }
  679. // set color of folder to new path
  680. const TSharedPtr<FLinearColor> FolderColor = LoadColor(SourcePath);
  681. if (FolderColor.IsValid())
  682. {
  683. SaveColor(SourcePath, nullptr);
  684. SaveColor(DestPath, FolderColor);
  685. }
  686. return true;
  687. }
  688. #endif
  689. bool ExtContentBrowserUtils::CopyFolders(const TArray<FString>& InSourcePathNames, const FString& DestPath)
  690. {
  691. TMap<FString, TArray<UObject*> > SourcePathToLoadedAssets;
  692. // Make sure the destination path is not in the source path list
  693. TArray<FString> SourcePathNames = InSourcePathNames;
  694. SourcePathNames.Remove(DestPath);
  695. // Load all assets in the source paths
  696. if (!PrepareFoldersForDragDrop(SourcePathNames, SourcePathToLoadedAssets))
  697. {
  698. return false;
  699. }
  700. // Load the Asset Registry to update paths during the copy
  701. FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked<FAssetRegistryModule>("AssetRegistry");
  702. // For every path which contained valid assets...
  703. for ( auto PathIt = SourcePathToLoadedAssets.CreateConstIterator(); PathIt; ++PathIt )
  704. {
  705. // Put dragged folders in a sub-folder under the destination path
  706. const FString SourcePath = PathIt.Key();
  707. FString SubFolderName = FPackageName::GetLongPackageAssetName(SourcePath);
  708. FString Destination = DestPath + TEXT("/") + SubFolderName;
  709. // Add the new path to notify sources views
  710. {
  711. // TSharedRef<FEmptyFolderVisibilityManager> EmptyFolderVisibilityManager = FContentBrowserSingleton::Get().GetEmptyFolderVisibilityManager();
  712. // EmptyFolderVisibilityManager->SetAlwaysShowPath(Destination);
  713. }
  714. AssetRegistryModule.Get().AddPath(Destination);
  715. // If any assets were in this path...
  716. if ( PathIt.Value().Num() > 0 )
  717. {
  718. // Copy assets and supply a source path to indicate it is relative
  719. ObjectTools::DuplicateObjects( PathIt.Value(), SourcePath, Destination, /*bOpenDialog=*/false );
  720. }
  721. const TSharedPtr<FLinearColor> FolderColor = LoadColor(SourcePath);
  722. if (FolderColor.IsValid())
  723. {
  724. SaveColor(Destination, FolderColor);
  725. }
  726. }
  727. return true;
  728. }
  729. #if ECB_LEGACY
  730. bool ExtContentBrowserUtils::MoveFolders(const TArray<FString>& InSourcePathNames, const FString& DestPath)
  731. {
  732. TMap<FString, TArray<UObject*> > SourcePathToLoadedAssets;
  733. FString DestPathWithTrailingSlash = DestPath / "";
  734. // Do not allow parent directories to be moved to themselves or children.
  735. TArray<FString> SourcePathNames = InSourcePathNames;
  736. TArray<FString> SourcePathNamesToRemove;
  737. for (auto SourcePathIt = SourcePathNames.CreateConstIterator(); SourcePathIt; ++SourcePathIt)
  738. {
  739. if(DestPathWithTrailingSlash.StartsWith(*SourcePathIt / ""))
  740. {
  741. SourcePathNamesToRemove.Add(*SourcePathIt);
  742. }
  743. }
  744. for (auto SourcePathToRemoveIt = SourcePathNamesToRemove.CreateConstIterator(); SourcePathToRemoveIt; ++SourcePathToRemoveIt)
  745. {
  746. SourcePathNames.Remove(*SourcePathToRemoveIt);
  747. }
  748. // Load all assets in the source paths
  749. if (!PrepareFoldersForDragDrop(SourcePathNames, SourcePathToLoadedAssets))
  750. {
  751. return false;
  752. }
  753. // Load the Asset Registry to update paths during the move
  754. FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked<FAssetRegistryModule>("AssetRegistry");
  755. // For every path which contained valid assets...
  756. for ( auto PathIt = SourcePathToLoadedAssets.CreateConstIterator(); PathIt; ++PathIt )
  757. {
  758. // Put dragged folders in a sub-folder under the destination path
  759. const FString SourcePath = PathIt.Key();
  760. const FString SubFolderName = FPackageName::GetLongPackageAssetName(SourcePath);
  761. const FString Destination = DestPathWithTrailingSlash + SubFolderName;
  762. // Add the new path to notify sources views
  763. {
  764. // TSharedRef<FEmptyFolderVisibilityManager> EmptyFolderVisibilityManager = FContentBrowserSingleton::Get().GetEmptyFolderVisibilityManager();
  765. // EmptyFolderVisibilityManager->SetAlwaysShowPath(Destination);
  766. }
  767. AssetRegistryModule.Get().AddPath(Destination);
  768. // If any assets were in this path...
  769. if ( PathIt.Value().Num() > 0 )
  770. {
  771. // Move assets and supply a source path to indicate it is relative
  772. MoveAssets( PathIt.Value(), Destination, PathIt.Key() );
  773. }
  774. // Attempt to remove the old path
  775. if (DeleteEmptyFolderFromDisk(SourcePath))
  776. {
  777. AssetRegistryModule.Get().RemovePath(SourcePath);
  778. }
  779. const TSharedPtr<FLinearColor> FolderColor = LoadColor(SourcePath);
  780. if (FolderColor.IsValid())
  781. {
  782. SaveColor(SourcePath, nullptr);
  783. SaveColor(Destination, FolderColor);
  784. }
  785. }
  786. return true;
  787. }
  788. #endif
  789. bool ExtContentBrowserUtils::PrepareFoldersForDragDrop(const TArray<FString>& SourcePathNames, TMap< FString, TArray<UObject*> >& OutSourcePathToLoadedAssets)
  790. {
  791. TSet<UObject*> AllFoundObjects;
  792. // Load the Asset Registry to update paths during the move
  793. FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked<FAssetRegistryModule>("AssetRegistry");
  794. // Check up-front how many assets we might load in this operation & warn the user
  795. TArray<FString> ObjectPathsToWarnAbout;
  796. for ( auto PathIt = SourcePathNames.CreateConstIterator(); PathIt; ++PathIt )
  797. {
  798. // Get all assets in this path
  799. TArray<FAssetData> AssetDataList;
  800. AssetRegistryModule.Get().GetAssetsByPath(FName(**PathIt), AssetDataList, true);
  801. for ( auto AssetIt = AssetDataList.CreateConstIterator(); AssetIt; ++AssetIt )
  802. {
  803. ObjectPathsToWarnAbout.Add((*AssetIt).GetSoftObjectPath().ToString());
  804. }
  805. }
  806. GWarn->BeginSlowTask(LOCTEXT("FolderDragDrop_Loading", "Loading folders"), true);
  807. // For every source path, load every package in the path (if necessary) and keep track of the assets that were loaded
  808. for ( auto PathIt = SourcePathNames.CreateConstIterator(); PathIt; ++PathIt )
  809. {
  810. // Get all assets in this path
  811. TArray<FAssetData> AssetDataList;
  812. AssetRegistryModule.Get().GetAssetsByPath(FName(**PathIt), AssetDataList, true);
  813. // Form a list of all object paths for these assets
  814. TArray<FString> ObjectPaths;
  815. for ( auto AssetIt = AssetDataList.CreateConstIterator(); AssetIt; ++AssetIt )
  816. {
  817. ObjectPaths.Add((*AssetIt).GetSoftObjectPath().ToString());
  818. }
  819. // Load all assets in this path if needed
  820. TArray<UObject*> AllLoadedAssets;
  821. LoadAssetsIfNeeded(ObjectPaths, AllLoadedAssets, false);
  822. // Add a slash to the end of the path so StartsWith doesn't get a false positive on similarly named folders
  823. const FString SourcePathWithSlash = *PathIt + TEXT("/");
  824. // Find all files in this path and subpaths
  825. TArray<FString> Filenames;
  826. FString RootFolder = FPackageName::LongPackageNameToFilename(SourcePathWithSlash);
  827. FPackageName::FindPackagesInDirectory(Filenames, RootFolder);
  828. // Now find all assets in memory that were loaded from this path that are valid for drag-droppping
  829. TArray<UObject*> ValidLoadedAssets;
  830. for ( auto AssetIt = AllLoadedAssets.CreateConstIterator(); AssetIt; ++AssetIt )
  831. {
  832. UObject* Asset = *AssetIt;
  833. if ( (Asset->GetClass() != UObjectRedirector::StaticClass() && // Skip object redirectors
  834. !AllFoundObjects.Contains(Asset) // Skip assets we have already found to avoid processing them twice
  835. ) )
  836. {
  837. ValidLoadedAssets.Add(Asset);
  838. AllFoundObjects.Add(Asset);
  839. }
  840. }
  841. // Add an entry of the map of source paths to assets found, whether any assets were found or not
  842. OutSourcePathToLoadedAssets.Add(*PathIt, ValidLoadedAssets);
  843. }
  844. GWarn->EndSlowTask();
  845. ensure(SourcePathNames.Num() == OutSourcePathToLoadedAssets.Num());
  846. return true;
  847. }
  848. void ExtContentBrowserUtils::CopyAssetReferencesToClipboard(const TArray<FAssetData>& AssetsToCopy)
  849. {
  850. FString ClipboardText;
  851. for ( auto AssetIt = AssetsToCopy.CreateConstIterator(); AssetIt; ++AssetIt)
  852. {
  853. if ( ClipboardText.Len() > 0 )
  854. {
  855. ClipboardText += LINE_TERMINATOR;
  856. }
  857. ClipboardText += (*AssetIt).GetExportTextName();
  858. }
  859. FPlatformApplicationMisc::ClipboardCopy( *ClipboardText );
  860. }
  861. void ExtContentBrowserUtils::CopyFilePathsToClipboard(const TArray<FExtAssetData>& AssetsToCopy)
  862. {
  863. FString ClipboardText;
  864. for (const FExtAssetData& Asset : AssetsToCopy)
  865. {
  866. if (ClipboardText.Len() > 0)
  867. {
  868. ClipboardText += LINE_TERMINATOR;
  869. }
  870. #if 0
  871. FString PackageFileName;
  872. FString PackageFile;
  873. if (FPackageName::TryConvertLongPackageNameToFilename(Asset.PackageName.ToString(), PackageFileName) &&
  874. FPackageName::FindPackageFileWithoutExtension(PackageFileName, PackageFile))
  875. {
  876. ClipboardText += FPaths::ConvertRelativePathToFull(PackageFile);
  877. }
  878. else
  879. {
  880. // Add a message for when a user tries to copy the path to a file that doesn't exist on disk of the form
  881. // <AssetName>: No file on disk
  882. ClipboardText += Asset.AssetName.ToString() + FString(": No file on disk");
  883. }
  884. #endif
  885. if (Asset.IsValid())
  886. {
  887. ClipboardText += Asset.PackageFilePath.ToString();
  888. }
  889. }
  890. FPlatformApplicationMisc::ClipboardCopy(*ClipboardText);
  891. }
  892. void ExtContentBrowserUtils::CaptureThumbnailFromViewport(FViewport* InViewport, const TArray<FAssetData>& InAssetsToAssign)
  893. {
  894. //capture the thumbnail
  895. uint32 SrcWidth = InViewport->GetSizeXY().X;
  896. uint32 SrcHeight = InViewport->GetSizeXY().Y;
  897. // Read the contents of the viewport into an array.
  898. TArray<FColor> OrigBitmap;
  899. if (InViewport->ReadPixels(OrigBitmap))
  900. {
  901. check(OrigBitmap.Num() == SrcWidth * SrcHeight);
  902. //pin to smallest value
  903. int32 CropSize = FMath::Min<uint32>(SrcWidth, SrcHeight);
  904. //pin to max size
  905. int32 ScaledSize = FMath::Min<uint32>(ThumbnailTools::DefaultThumbnailSize, CropSize);
  906. //calculations for cropping
  907. TArray<FColor> CroppedBitmap;
  908. CroppedBitmap.AddUninitialized(CropSize*CropSize);
  909. //Crop the image
  910. int32 CroppedSrcTop = (SrcHeight - CropSize)/2;
  911. int32 CroppedSrcLeft = (SrcWidth - CropSize)/2;
  912. for (int32 Row = 0; Row < CropSize; ++Row)
  913. {
  914. //Row*Side of a row*byte per color
  915. int32 SrcPixelIndex = (CroppedSrcTop+Row)*SrcWidth + CroppedSrcLeft;
  916. const void* SrcPtr = &(OrigBitmap[SrcPixelIndex]);
  917. void* DstPtr = &(CroppedBitmap[Row*CropSize]);
  918. FMemory::Memcpy(DstPtr, SrcPtr, CropSize*4);
  919. }
  920. //Scale image down if needed
  921. TArray<FColor> ScaledBitmap;
  922. if (ScaledSize < CropSize)
  923. {
  924. FImageUtils::ImageResize( CropSize, CropSize, CroppedBitmap, ScaledSize, ScaledSize, ScaledBitmap, true );
  925. }
  926. else
  927. {
  928. //just copy the data over. sizes are the same
  929. ScaledBitmap = CroppedBitmap;
  930. }
  931. //setup actual thumbnail
  932. FObjectThumbnail TempThumbnail;
  933. TempThumbnail.SetImageSize( ScaledSize, ScaledSize );
  934. TArray<uint8>& ThumbnailByteArray = TempThumbnail.AccessImageData();
  935. // Copy scaled image into destination thumb
  936. int32 MemorySize = ScaledSize*ScaledSize*sizeof(FColor);
  937. ThumbnailByteArray.AddUninitialized(MemorySize);
  938. FMemory::Memcpy(&(ThumbnailByteArray[0]), &(ScaledBitmap[0]), MemorySize);
  939. FAssetToolsModule& AssetToolsModule = FModuleManager::LoadModuleChecked<FAssetToolsModule>("AssetTools");
  940. //check if each asset should receive the new thumb nail
  941. for ( auto AssetIt = InAssetsToAssign.CreateConstIterator(); AssetIt; ++AssetIt )
  942. {
  943. const FAssetData& CurrentAsset = *AssetIt;
  944. //assign the thumbnail and dirty
  945. const FString ObjectFullName = CurrentAsset.GetFullName();
  946. const FString PackageName = CurrentAsset.PackageName.ToString();
  947. UPackage* AssetPackage = FindObject<UPackage>( NULL, *PackageName );
  948. if ( ensure(AssetPackage) )
  949. {
  950. FObjectThumbnail* NewThumbnail = ThumbnailTools::CacheThumbnail(ObjectFullName, &TempThumbnail, AssetPackage);
  951. if ( ensure(NewThumbnail) )
  952. {
  953. //we need to indicate that the package needs to be resaved
  954. AssetPackage->MarkPackageDirty();
  955. // Let the content browser know that we've changed the thumbnail
  956. NewThumbnail->MarkAsDirty();
  957. // Signal that the asset was changed if it is loaded so thumbnail pools will update
  958. if ( CurrentAsset.IsAssetLoaded() )
  959. {
  960. CurrentAsset.GetAsset()->PostEditChange();
  961. }
  962. //Set that thumbnail as a valid custom thumbnail so it'll be saved out
  963. NewThumbnail->SetCreatedAfterCustomThumbsEnabled();
  964. }
  965. }
  966. }
  967. }
  968. }
  969. void ExtContentBrowserUtils::ClearCustomThumbnails(const TArray<FAssetData>& InAssetsToAssign)
  970. {
  971. FAssetToolsModule& AssetToolsModule = FModuleManager::LoadModuleChecked<FAssetToolsModule>("AssetTools");
  972. //check if each asset should receive the new thumb nail
  973. for ( auto AssetIt = InAssetsToAssign.CreateConstIterator(); AssetIt; ++AssetIt )
  974. {
  975. const FAssetData& CurrentAsset = *AssetIt;
  976. // check whether this is a type that uses one of the shared static thumbnails
  977. if ( AssetToolsModule.Get().AssetUsesGenericThumbnail( CurrentAsset ) )
  978. {
  979. //assign the thumbnail and dirty
  980. const FString ObjectFullName = CurrentAsset.GetFullName();
  981. const FString PackageName = CurrentAsset.PackageName.ToString();
  982. UPackage* AssetPackage = FindObject<UPackage>( NULL, *PackageName );
  983. if ( ensure(AssetPackage) )
  984. {
  985. ThumbnailTools::CacheEmptyThumbnail( ObjectFullName, AssetPackage);
  986. //we need to indicate that the package needs to be resaved
  987. AssetPackage->MarkPackageDirty();
  988. // Signal that the asset was changed if it is loaded so thumbnail pools will update
  989. if ( CurrentAsset.IsAssetLoaded() )
  990. {
  991. CurrentAsset.GetAsset()->PostEditChange();
  992. }
  993. }
  994. }
  995. }
  996. }
  997. bool ExtContentBrowserUtils::AssetHasCustomThumbnail( const FAssetData& AssetData )
  998. {
  999. FAssetToolsModule& AssetToolsModule = FModuleManager::LoadModuleChecked<FAssetToolsModule>("AssetTools");
  1000. if ( AssetToolsModule.Get().AssetUsesGenericThumbnail(AssetData) )
  1001. {
  1002. return ThumbnailTools::AssetHasCustomThumbnail(AssetData.GetFullName());
  1003. }
  1004. return false;
  1005. }
  1006. ExtContentBrowserUtils::ECBFolderCategory ExtContentBrowserUtils::GetFolderCategory( const FString& InPath )
  1007. {
  1008. static const FString ClassesPrefix = TEXT("/Classes_");
  1009. static const FString GameClassesPrefix = TEXT("/Classes_Game");
  1010. static const FString EngineClassesPrefix = TEXT("/Classes_Engine");
  1011. const bool bIsClassDir = InPath.StartsWith(ClassesPrefix);
  1012. if(bIsClassDir)
  1013. {
  1014. const bool bIsGameClassDir = InPath.StartsWith(GameClassesPrefix);
  1015. if(bIsGameClassDir)
  1016. {
  1017. return ECBFolderCategory::GameClasses;
  1018. }
  1019. const bool bIsEngineClassDir = InPath.StartsWith(EngineClassesPrefix);
  1020. if(bIsEngineClassDir)
  1021. {
  1022. return ECBFolderCategory::EngineClasses;
  1023. }
  1024. return ECBFolderCategory::PluginClasses;
  1025. }
  1026. else
  1027. {
  1028. if (IsEngineFolder(InPath))
  1029. {
  1030. return ECBFolderCategory::EngineContent;
  1031. }
  1032. if (IsDevelopersFolder(InPath))
  1033. {
  1034. return ECBFolderCategory::DeveloperContent;
  1035. }
  1036. EPluginLoadedFrom PluginSource;
  1037. if (IsPluginFolder(InPath, &PluginSource))
  1038. {
  1039. if (PluginSource == EPluginLoadedFrom::Project)
  1040. {
  1041. return ECBFolderCategory::PluginContent;
  1042. }
  1043. else
  1044. {
  1045. checkSlow(PluginSource == EPluginLoadedFrom::Engine);
  1046. return ECBFolderCategory::EngineContent;
  1047. }
  1048. }
  1049. return ECBFolderCategory::GameContent;
  1050. }
  1051. }
  1052. ExtContentBrowserUtils::ECBFolderCategory ExtContentBrowserUtils::GetRootFolderCategory(const FString& InPath)
  1053. {
  1054. FText LocalizedFolderName = FText::GetEmpty();
  1055. FExtAssetData::EContentType ContentType;
  1056. if (FExtContentBrowserSingleton::GetAssetRegistry().QueryRootContentPathInfo(InPath, &LocalizedFolderName, &ContentType))
  1057. {
  1058. if (ContentType == FExtAssetData::EContentType::Plugin)
  1059. {
  1060. return ECBFolderCategory::PluginContent;
  1061. }
  1062. if (ContentType == FExtAssetData::EContentType::Project)
  1063. {
  1064. return ECBFolderCategory::ProjectContent;
  1065. }
  1066. if (ContentType == FExtAssetData::EContentType::VaultCache)
  1067. {
  1068. return ECBFolderCategory::VaultCacheContent;
  1069. }
  1070. }
  1071. return ECBFolderCategory::OrphanContent;
  1072. }
  1073. bool ExtContentBrowserUtils::IsEngineFolder( const FString& InPath )
  1074. {
  1075. static const FString EnginePathWithSlash = TEXT("/Engine");
  1076. static const FString EnginePathWithoutSlash = TEXT("Engine");
  1077. return InPath.StartsWith(EnginePathWithSlash) || InPath == EnginePathWithoutSlash;
  1078. }
  1079. bool ExtContentBrowserUtils::IsDevelopersFolder( const FString& InPath )
  1080. {
  1081. static const FString DeveloperPathWithSlash = FPackageName::FilenameToLongPackageName(FPaths::GameDevelopersDir());
  1082. static const FString DeveloperPathWithoutSlash = DeveloperPathWithSlash.LeftChop(1);
  1083. return InPath.StartsWith(DeveloperPathWithSlash) || InPath == DeveloperPathWithoutSlash;
  1084. }
  1085. static bool PathStartsWithPluginAssetPath(const FString& Path, const FString& PluginName)
  1086. {
  1087. // accepted path examples for a plugin named "Plugin":
  1088. // "/Plugin"
  1089. // "/Plugin/"
  1090. // "/Plugin/More/Stuff"
  1091. const int32 PluginNameLength = PluginName.Len();
  1092. const int32 PathLength = Path.Len();
  1093. if (PathLength <= PluginNameLength)
  1094. {
  1095. return false;
  1096. }
  1097. else
  1098. {
  1099. const TCHAR* PathCh = *Path;
  1100. return PathCh[0] == '/' && (PathCh[PluginNameLength + 1] == '/' || PathCh[PluginNameLength + 1] == 0) && FCString::Strnicmp(PathCh + 1, *PluginName, PluginNameLength) == 0;
  1101. }
  1102. }
  1103. bool ExtContentBrowserUtils::IsPluginFolder(const FString& InPath, const TArray<TSharedRef<IPlugin>>& InPlugins, EPluginLoadedFrom* OutPluginSource)
  1104. {
  1105. for (const TSharedRef<IPlugin>& PluginRef : InPlugins)
  1106. {
  1107. const IPlugin& Plugin = *PluginRef;
  1108. const FString& PluginName = Plugin.GetName();
  1109. if (PathStartsWithPluginAssetPath(InPath, PluginName) || InPath == PluginName)
  1110. {
  1111. if (OutPluginSource != nullptr)
  1112. {
  1113. *OutPluginSource = Plugin.GetLoadedFrom();
  1114. }
  1115. return true;
  1116. }
  1117. }
  1118. return false;
  1119. }
  1120. bool ExtContentBrowserUtils::IsPluginFolder(const FString& InPath, EPluginLoadedFrom* OutPluginSource)
  1121. {
  1122. return IsPluginFolder(InPath, IPluginManager::Get().GetEnabledPluginsWithContent(), OutPluginSource);
  1123. }
  1124. bool ExtContentBrowserUtils::IsClassesFolder(const FString& InPath)
  1125. {
  1126. // Strip off any leading or trailing forward slashes
  1127. // We just want the name without any path separators
  1128. FString CleanFolderPath = InPath;
  1129. while ( CleanFolderPath.StartsWith(TEXT("/")) )
  1130. {
  1131. CleanFolderPath = CleanFolderPath.Mid(1);
  1132. }
  1133. while ( CleanFolderPath.EndsWith(TEXT("/")) )
  1134. {
  1135. CleanFolderPath = CleanFolderPath.Mid(0, CleanFolderPath.Len() - 1);
  1136. }
  1137. static const FString ClassesPrefix = TEXT("Classes_");
  1138. const bool bIsClassDir = InPath.StartsWith(ClassesPrefix);
  1139. return bIsClassDir;
  1140. }
  1141. bool ExtContentBrowserUtils::IsLocalizationFolder( const FString& InPath )
  1142. {
  1143. return FPackageName::IsLocalizedPackage(InPath);
  1144. }
  1145. void ExtContentBrowserUtils::GetObjectsInAssetData(const TArray<FAssetData>& AssetList, TArray<UObject*>& OutDroppedObjects)
  1146. {
  1147. for (int32 AssetIdx = 0; AssetIdx < AssetList.Num(); ++AssetIdx)
  1148. {
  1149. const FAssetData& AssetData = AssetList[AssetIdx];
  1150. UObject* Obj = AssetData.GetAsset();
  1151. if (Obj)
  1152. {
  1153. OutDroppedObjects.Add(Obj);
  1154. }
  1155. }
  1156. }
  1157. bool ExtContentBrowserUtils::IsValidFolderName(const FString& FolderName, FText& Reason)
  1158. {
  1159. // Check length of the folder name
  1160. if ( FolderName.Len() == 0 )
  1161. {
  1162. Reason = LOCTEXT( "InvalidFolderName_IsTooShort", "Please provide a name for this folder." );
  1163. return false;
  1164. }
  1165. if ( FolderName.Len() > FPlatformMisc::GetMaxPathLength() )
  1166. {
  1167. Reason = FText::Format( LOCTEXT("InvalidFolderName_TooLongForCooking", "Filename '{0}' is too long; this may interfere with cooking for consoles. Unreal filenames should be no longer than {1} characters." ),
  1168. FText::FromString(FolderName), FText::AsNumber(FPlatformMisc::GetMaxPathLength()) );
  1169. return false;
  1170. }
  1171. const FString InvalidChars = INVALID_LONGPACKAGE_CHARACTERS TEXT("/[]"); // Slash is an invalid character for a folder name
  1172. // See if the name contains invalid characters.
  1173. FString Char;
  1174. for( int32 CharIdx = 0; CharIdx < FolderName.Len(); ++CharIdx )
  1175. {
  1176. Char = FolderName.Mid(CharIdx, 1);
  1177. if ( InvalidChars.Contains(*Char) )
  1178. {
  1179. FString ReadableInvalidChars = InvalidChars;
  1180. ReadableInvalidChars.ReplaceInline(TEXT("\r"), TEXT(""));
  1181. ReadableInvalidChars.ReplaceInline(TEXT("\n"), TEXT(""));
  1182. ReadableInvalidChars.ReplaceInline(TEXT("\t"), TEXT(""));
  1183. Reason = FText::Format(LOCTEXT("InvalidFolderName_InvalidCharacters", "A folder name may not contain any of the following characters: {0}"), FText::FromString(ReadableInvalidChars));
  1184. return false;
  1185. }
  1186. }
  1187. return FFileHelper::IsFilenameValidForSaving( FolderName, Reason );
  1188. }
  1189. bool ExtContentBrowserUtils::DoesFolderExist(const FString& FolderPath)
  1190. {
  1191. // todo: jdale - CLASS - Will need updating to handle class folders
  1192. TArray<FString> SubPaths;
  1193. FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked<FAssetRegistryModule>(TEXT("AssetRegistry"));
  1194. AssetRegistryModule.Get().GetSubPaths(FPaths::GetPath(FolderPath), SubPaths, false);
  1195. for(auto SubPathIt(SubPaths.CreateConstIterator()); SubPathIt; SubPathIt++)
  1196. {
  1197. if ( *SubPathIt == FolderPath )
  1198. {
  1199. return true;
  1200. }
  1201. }
  1202. return false;
  1203. }
  1204. bool ExtContentBrowserUtils::IsEmptyFolder(const FString& FolderPath, const bool bRecursive)
  1205. {
  1206. if (ExtContentBrowserUtils::IsClassPath(FolderPath))
  1207. {
  1208. // TSharedRef<FNativeClassHierarchy> NativeClassHierarchy = FContentBrowserSingleton::Get().GetNativeClassHierarchy();
  1209. // return !NativeClassHierarchy->HasClasses(*FolderPath, bRecursive);
  1210. }
  1211. else
  1212. {
  1213. FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked<FAssetRegistryModule>(TEXT("AssetRegistry"));
  1214. return !AssetRegistryModule.Get().HasAssets(*FolderPath, bRecursive);
  1215. }
  1216. return false;
  1217. }
  1218. bool ExtContentBrowserUtils::IsRootDir(const FString& FolderPath)
  1219. {
  1220. return IsAssetRootDir(FolderPath) || IsClassRootDir(FolderPath);
  1221. }
  1222. bool ExtContentBrowserUtils::IsAssetRootDir(const FString& FolderPath)
  1223. {
  1224. // All root asset folders start with "/" (not "/Classes_") and contain only a single / (at the beginning)
  1225. int32 LastSlashIndex = INDEX_NONE;
  1226. return FolderPath.Len() > 1 && !IsClassPath(FolderPath) && FolderPath.FindLastChar(TEXT('/'), LastSlashIndex) && LastSlashIndex == 0;
  1227. }
  1228. bool ExtContentBrowserUtils::IsClassRootDir(const FString& FolderPath)
  1229. {
  1230. // All root class folders start with "/Classes_" and contain only a single / (at the beginning)
  1231. int32 LastSlashIndex = INDEX_NONE;
  1232. return IsClassPath(FolderPath) && FolderPath.FindLastChar(TEXT('/'), LastSlashIndex) && LastSlashIndex == 0;
  1233. }
  1234. FText ExtContentBrowserUtils::GetRootDirDisplayName(const FString& FolderPath)
  1235. {
  1236. #if ECB_DISABLE
  1237. // Strip off any trailing forward slashes
  1238. FString CleanFolderPath = FolderPath;
  1239. while (CleanFolderPath.EndsWith(TEXT("/")))
  1240. {
  1241. CleanFolderPath = CleanFolderPath.Mid(0, CleanFolderPath.Len() - 1);
  1242. }
  1243. static const FString VaultCacheContentFolderName = TEXT("/data/Content");
  1244. static const FString VaultCacheDataFolderName = TEXT("/data");
  1245. static const FString ProjectOrPluginContentFolderName = TEXT("/Content");
  1246. if (CleanFolderPath.EndsWith(VaultCacheContentFolderName))
  1247. {
  1248. FString VaultCacheFolder = FPaths::ConvertRelativePathToFull(FPaths::Combine(CleanFolderPath, TEXT("../..")));
  1249. if (FExtContentDirFinder::FindWithFolder(VaultCacheFolder, TEXT("manifest"), /*bExtension*/ false))
  1250. {
  1251. CleanFolderPath = VaultCacheFolder;// CleanFolderPath.Left(CleanFolderPath.Len() - VaultCacheContentFolderName.Len());
  1252. }
  1253. }
  1254. else if (CleanFolderPath.EndsWith(VaultCacheDataFolderName))
  1255. {
  1256. FString VaultCacheFolder = FPaths::ConvertRelativePathToFull(FPaths::Combine(CleanFolderPath, TEXT("..")));
  1257. if (FExtContentDirFinder::FindWithFolder(VaultCacheFolder, TEXT("manifest"), /*bExtension*/ false))
  1258. {
  1259. CleanFolderPath = VaultCacheFolder;// CleanFolderPath.Left(CleanFolderPath.Len() - VaultCacheDataFolderName.Len());
  1260. }
  1261. }
  1262. else if (CleanFolderPath.EndsWith(ProjectOrPluginContentFolderName))
  1263. {
  1264. FString PluginFolder = FPaths::ConvertRelativePathToFull(FPaths::Combine(CleanFolderPath, TEXT("..")));
  1265. if (FExtContentDirFinder::FindWithFolder(PluginFolder, TEXT(".uplugin"), /*bExtension*/ true))
  1266. {
  1267. CleanFolderPath = PluginFolder;// CleanFolderPath.Left(CleanFolderPath.Len() - VaultCacheDataFolderName.Len());
  1268. }
  1269. }
  1270. else if (CleanFolderPath.EndsWith(ProjectOrPluginContentFolderName))
  1271. {
  1272. FString ProjectFolder = FPaths::ConvertRelativePathToFull(FPaths::Combine(CleanFolderPath, TEXT("..")));
  1273. if (FExtContentDirFinder::FindWithFolder(ProjectFolder, TEXT(".uproject"), /*bExtension*/ true))
  1274. {
  1275. CleanFolderPath = ProjectFolder;// CleanFolderPath.Left(CleanFolderPath.Len() - VaultCacheDataFolderName.Len());
  1276. }
  1277. }
  1278. FString DisplayFolderName = FPaths::GetPathLeaf(CleanFolderPath);
  1279. FText LocalizedFolderName = DisplayFolderName.IsEmpty() ? FText::FromString(CleanFolderPath) : FText::FromString(DisplayFolderName);
  1280. #endif
  1281. FText LocalizedFolderName = FText::GetEmpty();
  1282. FExtAssetData::EContentType ContentType;
  1283. if (!FExtContentBrowserSingleton::GetAssetRegistry().QueryRootContentPathInfo(FolderPath, &LocalizedFolderName, &ContentType))
  1284. {
  1285. // fallback
  1286. FString CleanFolderPath = FolderPath;
  1287. FPaths::NormalizeDirectoryName(CleanFolderPath);
  1288. FString DisplayFolderName = FPaths::GetPathLeaf(CleanFolderPath);
  1289. LocalizedFolderName = DisplayFolderName.IsEmpty() ? FText::FromString(CleanFolderPath) : FText::FromString(DisplayFolderName);
  1290. }
  1291. #if ECB_LEGACY
  1292. const bool bIsBackgroundGathering = FExtContentBrowserSingleton::GetAssetRegistry().IsFolderBackgroundGathering(FolderPath);
  1293. if (bIsBackgroundGathering)
  1294. {
  1295. return FText::Format(LOCTEXT("RootContentFolderFmt", "{0} (Loading...)"), LocalizedFolderName);
  1296. }
  1297. else
  1298. #endif
  1299. {
  1300. return FText::Format(LOCTEXT("RootContentFolderFmt", "{0}"), LocalizedFolderName);
  1301. }
  1302. }
  1303. bool ExtContentBrowserUtils::IsFolderBackgroundGathering(const FString& InFolder)
  1304. {
  1305. const bool bIsBackgroundGathering = FExtContentBrowserSingleton::GetAssetRegistry().IsFolderBackgroundGathering(InFolder);
  1306. return bIsBackgroundGathering;
  1307. }
  1308. FName ExtContentBrowserUtils::GetCurrentGatheringFolder()
  1309. {
  1310. return FExtContentBrowserSingleton::GetAssetRegistry().GetCurrentGatheringFolder();
  1311. }
  1312. bool ExtContentBrowserUtils::IsClassPath(const FString& InPath)
  1313. {
  1314. static const FString ClassesRootPrefix = TEXT("/Classes_");
  1315. return InPath.StartsWith(ClassesRootPrefix);
  1316. }
  1317. bool ExtContentBrowserUtils::IsCollectionPath(const FString& InPath, FName* OutCollectionName, ECollectionShareType::Type* OutCollectionShareType)
  1318. {
  1319. static const FString CollectionsRootPrefix = TEXT("/Collections");
  1320. if (InPath.StartsWith(CollectionsRootPrefix))
  1321. {
  1322. TArray<FString> PathParts;
  1323. InPath.ParseIntoArray(PathParts, TEXT("/"));
  1324. check(PathParts.Num() > 2);
  1325. // The second part of the path is the share type name
  1326. if (OutCollectionShareType)
  1327. {
  1328. *OutCollectionShareType = ECollectionShareType::FromString(*PathParts[1]);
  1329. }
  1330. // The third part of the path is the collection name
  1331. if (OutCollectionName)
  1332. {
  1333. *OutCollectionName = FName(*PathParts[2]);
  1334. }
  1335. return true;
  1336. }
  1337. return false;
  1338. }
  1339. void ExtContentBrowserUtils::CountPathTypes(const TArray<FString>& InPaths, int32& OutNumAssetPaths, int32& OutNumClassPaths)
  1340. {
  1341. OutNumAssetPaths = 0;
  1342. OutNumClassPaths = 0;
  1343. for(const FString& Path : InPaths)
  1344. {
  1345. if(IsClassPath(Path))
  1346. {
  1347. ++OutNumClassPaths;
  1348. }
  1349. else
  1350. {
  1351. ++OutNumAssetPaths;
  1352. }
  1353. }
  1354. }
  1355. void ExtContentBrowserUtils::CountPathTypes(const TArray<FName>& InPaths, int32& OutNumAssetPaths, int32& OutNumClassPaths)
  1356. {
  1357. OutNumAssetPaths = 0;
  1358. OutNumClassPaths = 0;
  1359. for(const FName& Path : InPaths)
  1360. {
  1361. if(IsClassPath(Path.ToString()))
  1362. {
  1363. ++OutNumClassPaths;
  1364. }
  1365. else
  1366. {
  1367. ++OutNumAssetPaths;
  1368. }
  1369. }
  1370. }
  1371. void ExtContentBrowserUtils::CountItemTypes(const TArray<FAssetData>& InItems, int32& OutNumAssetItems, int32& OutNumClassItems)
  1372. {
  1373. OutNumAssetItems = 0;
  1374. OutNumClassItems = 0;
  1375. for(const FAssetData& Item : InItems)
  1376. {
  1377. if(Item.AssetClassPath == FTopLevelAssetPath(TEXT("/Script/CoreUObject"), NAME_Class))
  1378. {
  1379. ++OutNumClassItems;
  1380. }
  1381. else
  1382. {
  1383. ++OutNumAssetItems;
  1384. }
  1385. }
  1386. }
  1387. bool ExtContentBrowserUtils::IsValidPathToCreateNewClass(const FString& InPath)
  1388. {
  1389. // Classes can currently only be added to game modules - if this is restricted, we can use IsClassPath here instead
  1390. // Classes can only be created in modules, so that will be at least two folders deep (two /)
  1391. static const FString GameClassesRootPrefix = TEXT("/Classes_Game");
  1392. int32 LastSlashIndex = INDEX_NONE;
  1393. return InPath.StartsWith(GameClassesRootPrefix) && InPath.FindLastChar(TEXT('/'), LastSlashIndex) && LastSlashIndex != 0;
  1394. }
  1395. bool ExtContentBrowserUtils::IsValidPathToCreateNewFolder(const FString& InPath)
  1396. {
  1397. // We can't currently make folders in class paths
  1398. // If we do later allow folders in class paths, they must only be created within modules (see IsValidPathToCreateNewClass above)
  1399. return !IsClassPath(InPath);
  1400. }
  1401. const TSharedPtr<FLinearColor> ExtContentBrowserUtils::LoadColor(const FString& FolderPath, bool bNoCache)
  1402. {
  1403. auto LoadColorInternal = [](const FString& InPath, bool bInNoCache) -> TSharedPtr<FLinearColor>
  1404. {
  1405. // See if we have a value cached first
  1406. if (!bInNoCache)
  1407. {
  1408. TSharedPtr<FLinearColor> CachedColor = PathColors.FindRef(InPath);
  1409. if (CachedColor.IsValid())
  1410. {
  1411. return CachedColor;
  1412. }
  1413. }
  1414. // Loads the color of folder at the given path from the config
  1415. if(FPaths::FileExists(GEditorPerProjectIni))
  1416. {
  1417. // Create a new entry from the config, skip if it's default
  1418. FString ColorStr;
  1419. if(GConfig->GetString(TEXT("PathColor"), *InPath, ColorStr, GEditorPerProjectIni))
  1420. {
  1421. FLinearColor Color;
  1422. if(Color.InitFromString(ColorStr) && !Color.Equals(ExtContentBrowserUtils::GetDefaultColor()))
  1423. {
  1424. return PathColors.Add(InPath, MakeShareable(new FLinearColor(Color)));
  1425. }
  1426. }
  1427. else if (!bInNoCache)
  1428. {
  1429. return PathColors.Add(InPath, MakeShareable(new FLinearColor(ExtContentBrowserUtils::GetDefaultColor())));
  1430. }
  1431. }
  1432. return nullptr;
  1433. };
  1434. // First try and find the color using the given path, as this works correctly for both assets and classes
  1435. TSharedPtr<FLinearColor> FoundColor = LoadColorInternal(FolderPath, bNoCache);
  1436. if(FoundColor.IsValid())
  1437. {
  1438. return FoundColor;
  1439. }
  1440. // If that failed, try and use the filename (assets used to use this as their color key, but it doesn't work with classes)
  1441. if(!IsClassPath(FolderPath))
  1442. {
  1443. const FString RelativePath = FPackageName::LongPackageNameToFilename(FolderPath + TEXT("/"));
  1444. return LoadColorInternal(RelativePath, bNoCache);
  1445. }
  1446. return nullptr;
  1447. }
  1448. bool ExtContentBrowserUtils::LoadFolderColor(const FString& FolderPath, FLinearColor& OutFolderColor)
  1449. {
  1450. return FExtContentBrowserSingleton::GetAssetRegistry().GetFolderColor(FolderPath, OutFolderColor);
  1451. }
  1452. void ExtContentBrowserUtils::SaveColor(const FString& FolderPath, const TSharedPtr<FLinearColor>& FolderColor, bool bForceAdd)
  1453. {
  1454. auto SaveColorInternal = [](const FString& InPath, const TSharedPtr<FLinearColor>& InFolderColor)
  1455. {
  1456. // Saves the color of the folder to the config
  1457. if(FPaths::FileExists(GEditorPerProjectIni))
  1458. {
  1459. GConfig->SetString(TEXT("PathColor"), *InPath, *InFolderColor->ToString(), GEditorPerProjectIni);
  1460. }
  1461. // Update the map too
  1462. PathColors.Add(InPath, InFolderColor);
  1463. };
  1464. auto RemoveColorInternal = [](const FString& InPath)
  1465. {
  1466. // Remove the color of the folder from the config
  1467. if(FPaths::FileExists(GEditorPerProjectIni))
  1468. {
  1469. GConfig->RemoveKey(TEXT("PathColor"), *InPath, GEditorPerProjectIni);
  1470. }
  1471. // Update the map too
  1472. PathColors.Remove(InPath);
  1473. };
  1474. // Remove the color if it's invalid or default
  1475. const bool bRemove = !FolderColor.IsValid() || (!bForceAdd && FolderColor->Equals(ExtContentBrowserUtils::GetDefaultColor()));
  1476. if(bRemove)
  1477. {
  1478. RemoveColorInternal(FolderPath);
  1479. }
  1480. else
  1481. {
  1482. SaveColorInternal(FolderPath, FolderColor);
  1483. }
  1484. // Make sure and remove any colors using the legacy path format
  1485. if(!IsClassPath(FolderPath))
  1486. {
  1487. const FString RelativePath = FPackageName::LongPackageNameToFilename(FolderPath + TEXT("/"));
  1488. return RemoveColorInternal(RelativePath);
  1489. }
  1490. }
  1491. bool ExtContentBrowserUtils::HasCustomColors( TArray< FLinearColor >* OutColors )
  1492. {
  1493. // Check to see how many paths are currently using this color
  1494. // Note: we have to use the config, as paths which haven't been rendered yet aren't registered in the map
  1495. bool bHasCustom = false;
  1496. if (FPaths::FileExists(GEditorPerProjectIni))
  1497. {
  1498. // Read individual entries from a config file.
  1499. TArray< FString > Section;
  1500. GConfig->GetSection( TEXT("PathColor"), Section, GEditorPerProjectIni );
  1501. for( int32 SectionIndex = 0; SectionIndex < Section.Num(); SectionIndex++ )
  1502. {
  1503. FString EntryStr = Section[ SectionIndex ];
  1504. EntryStr.TrimStartInline();
  1505. FString PathStr;
  1506. FString ColorStr;
  1507. if ( EntryStr.Split( TEXT( "=" ), &PathStr, &ColorStr ) )
  1508. {
  1509. // Ignore any that have invalid or default colors
  1510. FLinearColor CurrentColor;
  1511. if( CurrentColor.InitFromString( ColorStr ) && !CurrentColor.Equals( ExtContentBrowserUtils::GetDefaultColor() ) )
  1512. {
  1513. bHasCustom = true;
  1514. if ( OutColors )
  1515. {
  1516. // Only add if not already present (ignores near matches too)
  1517. bool bAdded = false;
  1518. for( int32 ColorIndex = 0; ColorIndex < OutColors->Num(); ColorIndex++ )
  1519. {
  1520. const FLinearColor& Color = (*OutColors)[ ColorIndex ];
  1521. if( CurrentColor.Equals( Color ) )
  1522. {
  1523. bAdded = true;
  1524. break;
  1525. }
  1526. }
  1527. if ( !bAdded )
  1528. {
  1529. OutColors->Add( CurrentColor );
  1530. }
  1531. }
  1532. else
  1533. {
  1534. break;
  1535. }
  1536. }
  1537. }
  1538. }
  1539. }
  1540. return bHasCustom;
  1541. }
  1542. FLinearColor ExtContentBrowserUtils::GetDefaultColor()
  1543. {
  1544. // The default tint the folder should appear as
  1545. return FLinearColor::Gray;
  1546. }
  1547. FText ExtContentBrowserUtils::GetExploreFolderText()
  1548. {
  1549. FFormatNamedArguments Args;
  1550. Args.Add(TEXT("FileManagerName"), FPlatformMisc::GetFileManagerName());
  1551. return FText::Format(NSLOCTEXT("GenericPlatform", "ShowInFileManager", "Show in {FileManagerName}"), Args);
  1552. }
  1553. #if ECB_DISABLE
  1554. static const auto CVarMaxFullPathLength =
  1555. IConsoleManager::Get().RegisterConsoleVariable( TEXT("MaxAssetFullPath"), FPlatformMisc::GetMaxPathLength(), TEXT("Maximum full path name of an asset.") )->AsVariableInt();
  1556. bool ExtContentBrowserUtils::IsValidObjectPathForCreate(const FString& ObjectPath, FText& OutErrorMessage, bool bAllowExistingAsset)
  1557. {
  1558. const FString ObjectName = FPackageName::ObjectPathToObjectName(ObjectPath);
  1559. // Make sure the name is not already a class or otherwise invalid for saving
  1560. if ( !FFileHelper::IsFilenameValidForSaving(ObjectName, OutErrorMessage) )
  1561. {
  1562. // Return false to indicate that the user should enter a new name
  1563. return false;
  1564. }
  1565. // Make sure the new name only contains valid characters
  1566. if ( !FName::IsValidXName( ObjectName, INVALID_OBJECTNAME_CHARACTERS INVALID_LONGPACKAGE_CHARACTERS, &OutErrorMessage ) )
  1567. {
  1568. // Return false to indicate that the user should enter a new name
  1569. return false;
  1570. }
  1571. // Make sure we are not creating an FName that is too large
  1572. if ( ObjectPath.Len() > NAME_SIZE )
  1573. {
  1574. // This asset already exists at this location, inform the user and continue
  1575. OutErrorMessage = LOCTEXT("AssetNameTooLong", "This asset name is too long. Please choose a shorter name.");
  1576. // Return false to indicate that the user should enter a new name
  1577. return false;
  1578. }
  1579. const FString PackageName = FPackageName::ObjectPathToPackageName(ObjectPath);
  1580. if (!IsValidPackageForCooking(PackageName, OutErrorMessage))
  1581. {
  1582. return false;
  1583. }
  1584. // Make sure we are not creating an path that is too long for the OS
  1585. const FString RelativePathFilename = FPackageName::LongPackageNameToFilename(PackageName, FPackageName::GetAssetPackageExtension()); // full relative path with name + extension
  1586. const FString FullPath = FPaths::ConvertRelativePathToFull(RelativePathFilename); // path to file on disk
  1587. if ( ObjectPath.Len() > (FPlatformMisc::GetMaxPathLength() - MAX_CLASS_NAME_LENGTH) || FullPath.Len() > CVarMaxFullPathLength->GetValueOnGameThread() )
  1588. {
  1589. // The full path for the asset is too long
  1590. OutErrorMessage = FText::Format( LOCTEXT("AssetPathTooLong",
  1591. "The full path for the asset is too deep, the maximum is '{0}'. \nPlease choose a shorter name for the asset or create it in a shallower folder structure."),
  1592. FText::AsNumber(FPlatformMisc::GetMaxPathLength()) );
  1593. // Return false to indicate that the user should enter a new name
  1594. return false;
  1595. }
  1596. // Check for an existing asset, unless it we were asked not to.
  1597. if ( !bAllowExistingAsset )
  1598. {
  1599. FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked<FAssetRegistryModule>("AssetRegistry");
  1600. FAssetData ExistingAsset = AssetRegistryModule.Get().GetAssetByObjectPath(FName(*ObjectPath));
  1601. if (ExistingAsset.IsValid())
  1602. {
  1603. // This asset already exists at this location, inform the user and continue
  1604. OutErrorMessage = FText::Format( LOCTEXT("RenameAssetAlreadyExists", "An asset already exists at this location with the name '{0}'."), FText::FromString( ObjectName ) );
  1605. // Return false to indicate that the user should enter a new name
  1606. return false;
  1607. }
  1608. }
  1609. return true;
  1610. }
  1611. #endif
  1612. bool ExtContentBrowserUtils::IsValidFolderPathForCreate(const FString& InFolderPath, const FString& NewFolderName, FText& OutErrorMessage)
  1613. {
  1614. if (!ExtContentBrowserUtils::IsValidFolderName(NewFolderName, OutErrorMessage))
  1615. {
  1616. return false;
  1617. }
  1618. const FString NewFolderPath = InFolderPath / NewFolderName;
  1619. if (ExtContentBrowserUtils::DoesFolderExist(NewFolderPath))
  1620. {
  1621. OutErrorMessage = LOCTEXT("RenameFolderAlreadyExists", "A folder already exists at this location with this name.");
  1622. return false;
  1623. }
  1624. // Make sure we are not creating a folder path that is too long
  1625. if (NewFolderPath.Len() > FPlatformMisc::GetMaxPathLength() - MAX_CLASS_NAME_LENGTH)
  1626. {
  1627. // The full path for the folder is too long
  1628. OutErrorMessage = FText::Format(LOCTEXT("RenameFolderPathTooLong",
  1629. "The full path for the folder is too deep, the maximum is '{0}'. Please choose a shorter name for the folder or create it in a shallower folder structure."),
  1630. FText::AsNumber(FPlatformMisc::GetMaxPathLength()));
  1631. // Return false to indicate that the user should enter a new name for the folder
  1632. return false;
  1633. }
  1634. const bool bDisplayL10N = GetDefault<UExtContentBrowserSettings>()->GetDisplayL10NFolder();
  1635. if (!bDisplayL10N && ExtContentBrowserUtils::IsLocalizationFolder(NewFolderPath))
  1636. {
  1637. OutErrorMessage = LOCTEXT("LocalizationFolderReserved", "The L10N folder is reserved for localized content and is currently hidden.");
  1638. return false;
  1639. }
  1640. return true;
  1641. }
  1642. int32 ExtContentBrowserUtils::GetPackageLengthForCooking(const FString& PackageName, bool IsInternalBuild)
  1643. {
  1644. // We assume the game name is 20 characters (the maximum allowed) to make sure that content can be ported between projects
  1645. static const int32 MaxGameNameLen = 20;
  1646. // Pad out the game name to the maximum allowed
  1647. const FString GameName = FApp::GetProjectName();
  1648. FString GameNamePadded = GameName;
  1649. while (GameNamePadded.Len() < MaxGameNameLen)
  1650. {
  1651. GameNamePadded += TEXT(" ");
  1652. }
  1653. // We use "WindowsNoEditor" below as it's the longest platform name, so will also prove that any shorter platform names will validate correctly
  1654. const FString AbsoluteRootPath = FPaths::ConvertRelativePathToFull(FPaths::RootDir());
  1655. const FString AbsoluteGamePath = FPaths::ConvertRelativePathToFull(FPaths::ProjectDir());
  1656. const FString AbsoluteEnginePath = FPaths::ConvertRelativePathToFull(FPaths::EngineDir());
  1657. const FString AbsoluteEngineCookPath = AbsoluteGamePath / TEXT("Saved") / TEXT("Cooked") / TEXT("WindowsNoEditor") / TEXT("Engine");
  1658. const FString AbsoluteGameCookPath = AbsoluteGamePath / TEXT("Saved") / TEXT("Cooked") / TEXT("WindowsNoEditor") / GameName;
  1659. EPluginLoadedFrom PluginLoadedFrom;
  1660. const bool bIsPluginAsset = ExtContentBrowserUtils::IsPluginFolder(PackageName, &PluginLoadedFrom);
  1661. const bool bIsEngineAsset = ExtContentBrowserUtils::IsEngineFolder(PackageName) || (bIsPluginAsset && PluginLoadedFrom == EPluginLoadedFrom::Engine);
  1662. const bool bIsProjectAsset = !bIsEngineAsset;
  1663. int32 AbsoluteCookPathToAssetLength = 0;
  1664. FString RelativePathToAsset;
  1665. const FString AbsolutePath = bIsEngineAsset ? AbsoluteEnginePath : AbsoluteGamePath;
  1666. const FString& AbsoluteCookPath = bIsEngineAsset ? AbsoluteEngineCookPath : AbsoluteGameCookPath;
  1667. if(FPackageName::TryConvertLongPackageNameToFilename(PackageName, RelativePathToAsset, FPackageName::GetAssetPackageExtension()))
  1668. {
  1669. const FString AbsolutePathToAsset = FPaths::ConvertRelativePathToFull(RelativePathToAsset);
  1670. FString AssetPathWithinCookDir = AbsolutePathToAsset;
  1671. FPaths::RemoveDuplicateSlashes(AssetPathWithinCookDir);
  1672. AssetPathWithinCookDir.RemoveFromStart(AbsolutePath, ESearchCase::CaseSensitive);
  1673. if (IsInternalBuild)
  1674. {
  1675. // We assume a constant size for the build machine base path, so strip either the root or game path from the start
  1676. // (depending on whether the project is part of the main UE4 source tree or located elsewhere)
  1677. FString CookDirWithoutBasePath = AbsoluteCookPath;
  1678. if (CookDirWithoutBasePath.StartsWith(AbsoluteRootPath, ESearchCase::CaseSensitive))
  1679. {
  1680. CookDirWithoutBasePath.RemoveFromStart(AbsoluteRootPath, ESearchCase::CaseSensitive);
  1681. }
  1682. else
  1683. {
  1684. CookDirWithoutBasePath.RemoveFromStart(AbsoluteCookPath, ESearchCase::CaseSensitive);
  1685. }
  1686. FString AbsoluteBuildMachineCookPathToAsset = FString(TEXT("D:/BuildFarm/buildmachine_++depot+UE4-Releases+4.10")) / CookDirWithoutBasePath / AssetPathWithinCookDir;
  1687. // only add game name padding if it is not an engine asset, otherwise it is considered portable already
  1688. if(!bIsEngineAsset)
  1689. {
  1690. AbsoluteBuildMachineCookPathToAsset.ReplaceInline(*GameName, *GameNamePadded, ESearchCase::CaseSensitive);
  1691. }
  1692. AbsoluteCookPathToAssetLength = AbsoluteBuildMachineCookPathToAsset.Len();
  1693. }
  1694. else
  1695. {
  1696. // Test that the package can be cooked based on the current project path
  1697. FString AbsoluteCookPathToAsset = AbsoluteCookPath / AssetPathWithinCookDir;
  1698. // only add game name padding if it is not an engine asset, otherwise it is considered portable already
  1699. if (!bIsEngineAsset)
  1700. {
  1701. AbsoluteCookPathToAsset.ReplaceInline(*GameName, *GameNamePadded, ESearchCase::CaseSensitive);
  1702. }
  1703. AbsoluteCookPathToAssetLength = AbsoluteCookPathToAsset.Len();
  1704. }
  1705. }
  1706. else
  1707. {
  1708. // UE_LOG(LogContentBrowser, Error, TEXT("Package Name '%' is not a valid path and cannot be converted to a filename"), *PackageName);
  1709. }
  1710. return AbsoluteCookPathToAssetLength;
  1711. }
  1712. bool ExtContentBrowserUtils::IsValidPackageForCooking(const FString& PackageName, FText& OutErrorMessage)
  1713. {
  1714. int32 AbsoluteCookPathToAssetLength = GetPackageLengthForCooking(PackageName, FEngineBuildSettings::IsInternalBuild());
  1715. int32 MaxCookPathLen = GetMaxCookPathLen();
  1716. if (AbsoluteCookPathToAssetLength > MaxCookPathLen)
  1717. {
  1718. // See TTP# 332328:
  1719. // The following checks are done mostly to prevent / alleviate the problems that "long" paths are causing with the BuildFarm and cooked builds.
  1720. // The BuildFarm uses a verbose path to encode extra information to provide more information when things fail, however this makes the path limitation a problem.
  1721. // - We assume a base path of D:/BuildFarm/buildmachine_++depot+UE4-Releases+4.10/
  1722. // - We assume the game name is 20 characters (the maximum allowed) to make sure that content can be ported between projects
  1723. // - We calculate the cooked game path relative to the game root (eg, Showcases/Infiltrator/Saved/Cooked/WindowsNoEditor/Infiltrator)
  1724. // - We calculate the asset path relative to (and including) the Content directory (eg, Content/Environment/Infil1/Infil1_Underground/Infrastructure/Model/SM_Infil1_Tunnel_Ceiling_Pipes_1xEntryCurveOuter_Double.uasset)
  1725. if (FEngineBuildSettings::IsInternalBuild())
  1726. {
  1727. // The projected length of the path for cooking is too long
  1728. OutErrorMessage = FText::Format(LOCTEXT("AssetCookingPathTooLongForBuildMachine", "The path to the asset is too long '{0}' for cooking by the build machines, the maximum is '{1}'\nPlease choose a shorter name for the asset or create it in a shallower folder structure with shorter folder names."), FText::AsNumber(AbsoluteCookPathToAssetLength), FText::AsNumber(MaxCookPathLen));
  1729. }
  1730. else
  1731. {
  1732. // The projected length of the path for cooking is too long
  1733. OutErrorMessage = FText::Format(LOCTEXT("AssetCookingPathTooLong", "The path to the asset is too long '{0}', the maximum for cooking is '{1}'\nPlease choose a shorter name for the asset or create it in a shallower folder structure with shorter folder names."), FText::AsNumber(AbsoluteCookPathToAssetLength), FText::AsNumber(MaxCookPathLen));
  1734. }
  1735. // Return false to indicate that the user should enter a new name
  1736. return false;
  1737. }
  1738. return true;
  1739. }
  1740. /** Given an set of packages that will be synced by a SCC operation, report any dependencies that are out-of-date and aren't in the list of packages to be synced */
  1741. void GetOutOfDatePackageDependencies(const TArray<FString>& InPackagesThatWillBeSynced, TArray<FString>& OutDependenciesThatAreOutOfDate)
  1742. {
  1743. #if ECB_LEGACY
  1744. FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked<FAssetRegistryModule>(TEXT("AssetRegistry"));
  1745. // Build up the initial list of known packages
  1746. // We add to these as we find new dependencies to process
  1747. TSet<FName> AllPackages;
  1748. TArray<FName> AllPackagesArray;
  1749. {
  1750. AllPackages.Reserve(InPackagesThatWillBeSynced.Num());
  1751. AllPackagesArray.Reserve(InPackagesThatWillBeSynced.Num());
  1752. for (const FString& PackageName : InPackagesThatWillBeSynced)
  1753. {
  1754. const FName PackageFName = *PackageName;
  1755. AllPackages.Emplace(PackageFName);
  1756. AllPackagesArray.Emplace(PackageFName);
  1757. }
  1758. }
  1759. // Build up the complete set of package dependencies
  1760. TArray<FString> AllDependencies;
  1761. {
  1762. for (int32 PackageIndex = 0; PackageIndex < AllPackagesArray.Num(); ++PackageIndex)
  1763. {
  1764. const FName PackageName = AllPackagesArray[PackageIndex];
  1765. TArray<FName> PackageDependencies;
  1766. AssetRegistryModule.GetDependencies(PackageName, PackageDependencies, EAssetRegistryDependencyType::Packages);
  1767. for (const FName PackageDependency : PackageDependencies)
  1768. {
  1769. if (!AllPackages.Contains(PackageDependency))
  1770. {
  1771. AllPackages.Emplace(PackageDependency);
  1772. AllPackagesArray.Emplace(PackageDependency);
  1773. FString PackageDependencyStr = PackageDependency.ToString();
  1774. if (!FPackageName::IsScriptPackage(PackageDependencyStr) && FPackageName::IsValidLongPackageName(PackageDependencyStr))
  1775. {
  1776. AllDependencies.Emplace(MoveTemp(PackageDependencyStr));
  1777. }
  1778. }
  1779. }
  1780. }
  1781. }
  1782. // Query SCC to see which dependencies are out-of-date
  1783. if (AllDependencies.Num() > 0)
  1784. {
  1785. ISourceControlProvider& SCCProvider = ISourceControlModule::Get().GetProvider();
  1786. TArray<FString> DependencyFilenames = SourceControlHelpers::PackageFilenames(AllDependencies);
  1787. for (int32 DependencyIndex = 0; DependencyIndex < AllDependencies.Num(); ++DependencyIndex)
  1788. {
  1789. // Dependency data may contain files that no longer exist on disk; strip those from the list now
  1790. if (!FPaths::FileExists(DependencyFilenames[DependencyIndex]))
  1791. {
  1792. AllDependencies.RemoveAt(DependencyIndex, 1, false);
  1793. DependencyFilenames.RemoveAt(DependencyIndex, 1, false);
  1794. --DependencyIndex;
  1795. }
  1796. }
  1797. SCCProvider.Execute(ISourceControlOperation::Create<FUpdateStatus>(), DependencyFilenames);
  1798. for (int32 DependencyIndex = 0; DependencyIndex < AllDependencies.Num(); ++DependencyIndex)
  1799. {
  1800. const FString& DependencyName = AllDependencies[DependencyIndex];
  1801. const FString& DependencyFilename = DependencyFilenames[DependencyIndex];
  1802. FSourceControlStatePtr SCCState = SCCProvider.GetState(DependencyFilename, EStateCacheUsage::Use);
  1803. if (SCCState.IsValid() && !SCCState->IsCurrent())
  1804. {
  1805. OutDependenciesThatAreOutOfDate.Emplace(DependencyName);
  1806. }
  1807. }
  1808. }
  1809. #endif
  1810. }
  1811. void ShowSyncDependenciesDialog(const TArray<FString>& InDependencies, TArray<FString>& OutExtraPackagesToSync)
  1812. {
  1813. #if ECB_LEGACY
  1814. if (InDependencies.Num() > 0)
  1815. {
  1816. FPackagesDialogModule& PackagesDialogModule = FModuleManager::LoadModuleChecked<FPackagesDialogModule>(TEXT("PackagesDialog"));
  1817. PackagesDialogModule.CreatePackagesDialog(
  1818. LOCTEXT("SyncAssetDependenciesTitle", "Sync Asset Dependencies"),
  1819. LOCTEXT("SyncAssetDependenciesMessage", "The following assets have newer versions available, but aren't selected to be synced.\nSelect any additional dependencies you would like to sync in order to avoid potential issues loading the updated packages.")
  1820. );
  1821. PackagesDialogModule.AddButton(
  1822. DRT_CheckOut,
  1823. LOCTEXT("SyncDependenciesButton", "Sync"),
  1824. LOCTEXT("SyncDependenciesButtonTip", "Sync with the selected dependencies included")
  1825. );
  1826. for (const FString& DependencyName : InDependencies)
  1827. {
  1828. UPackage* Package = FindPackage(nullptr, *DependencyName);
  1829. PackagesDialogModule.AddPackageItem(Package, DependencyName, ECheckBoxState::Checked);
  1830. }
  1831. const EDialogReturnType UserResponse = PackagesDialogModule.ShowPackagesDialog();
  1832. if (UserResponse == DRT_CheckOut)
  1833. {
  1834. TArray<UPackage*> SelectedPackages;
  1835. PackagesDialogModule.GetResults(SelectedPackages, ECheckBoxState::Checked);
  1836. for (UPackage* SelectedPackage : SelectedPackages)
  1837. {
  1838. if (SelectedPackage)
  1839. {
  1840. OutExtraPackagesToSync.Emplace(SelectedPackage->GetName());
  1841. }
  1842. }
  1843. }
  1844. }
  1845. #endif
  1846. }
  1847. bool ExtContentBrowserUtils::IsFavoriteFolder(const FString& FolderPath)
  1848. {
  1849. return FExtContentBrowserSingleton::Get().FavoriteFolderPaths.Contains(FolderPath);
  1850. }
  1851. void ExtContentBrowserUtils::AddFavoriteFolder(const FString& FolderPath, bool bFlushConfig /*= true*/)
  1852. {
  1853. FExtContentBrowserSingleton::Get().FavoriteFolderPaths.AddUnique(FolderPath);
  1854. }
  1855. void ExtContentBrowserUtils::RemoveFavoriteFolder(const FString& FolderPath, bool bFlushConfig /*= true*/)
  1856. {
  1857. TArray<FString> FoldersToRemove;
  1858. FoldersToRemove.Add(FolderPath);
  1859. // Find and remove any subfolders
  1860. for (const FString& FavoritePath : FExtContentBrowserSingleton::Get().FavoriteFolderPaths)
  1861. {
  1862. if (FavoritePath.StartsWith(FolderPath + TEXT("/")))
  1863. {
  1864. FoldersToRemove.Add(FavoritePath);
  1865. }
  1866. }
  1867. for (const FString& FolderToRemove : FoldersToRemove)
  1868. {
  1869. FExtContentBrowserSingleton::Get().FavoriteFolderPaths.Remove(FolderToRemove);
  1870. }
  1871. if (bFlushConfig)
  1872. {
  1873. GConfig->Flush(false, GEditorPerProjectIni);
  1874. }
  1875. }
  1876. const TArray<FString>& ExtContentBrowserUtils::GetFavoriteFolders()
  1877. {
  1878. return FExtContentBrowserSingleton::Get().FavoriteFolderPaths;
  1879. }
  1880. int32 ExtContentBrowserUtils::GetMaxCookPathLen()
  1881. {
  1882. if (GetDefault<UEditorExperimentalSettings>()->bEnableLongPathsSupport)
  1883. {
  1884. // Allow the longest path allowed by the system
  1885. return FPlatformMisc::GetMaxPathLength();
  1886. }
  1887. else
  1888. {
  1889. // 260 characters is the limit on Windows, which is the shortest max path of any platforms that support cooking
  1890. return 260;
  1891. }
  1892. }
  1893. void ExtContentBrowserUtils::BeginAdvancedCopyPackages(TArray<FAssetData>& AssetList, TArray<FString>& AssetPaths, FString& DestinationPath)
  1894. {
  1895. TMap<FString, TArray<UObject*> > SourcePathToLoadedAssets;
  1896. FString DestPathWithTrailingSlash = DestinationPath / "";
  1897. // Get a list of package names for input into Advanced Copy
  1898. TArray<FName> InputNames;
  1899. // Do not allow parent directories to be moved to themselves or children.
  1900. TArray<FString> SourcePathNames = AssetPaths;
  1901. for (int32 AssetIdx = 0; AssetIdx < AssetList.Num(); ++AssetIdx)
  1902. {
  1903. InputNames.Add(AssetList[AssetIdx].PackageName);
  1904. }
  1905. // Add any paths from selected folders
  1906. for (const FString AssetPath : AssetPaths)
  1907. {
  1908. InputNames.Add(FName(*AssetPath));
  1909. }
  1910. FAssetToolsModule& AssetToolsModule = FModuleManager::Get().LoadModuleChecked<FAssetToolsModule>("AssetTools");
  1911. AssetToolsModule.Get().BeginAdvancedCopyPackages(InputNames, DestPathWithTrailingSlash);
  1912. }
  1913. #undef LOCTEXT_NAMESPACE