2014-03-14 14:13:41 -04:00
// Copyright 1998-2014 Epic Games, Inc. All Rights Reserved.
# include "ContentBrowserPCH.h"
# include "Editor/UnrealEd/Public/Toolkits/AssetEditorManager.h"
# include "PackageTools.h"
# include "ObjectTools.h"
# include "ImageUtils.h"
# include "ISourceControlModule.h"
2014-04-02 18:09:23 -04:00
# include "MessageLog.h"
2014-08-26 11:11:44 -04:00
# include "EngineBuildSettings.h"
2014-03-14 14:13:41 -04:00
# define LOCTEXT_NAMESPACE "ContentBrowser"
2014-08-15 16:14:32 -04:00
# define MAX_CLASS_NAME_LENGTH 32 // Enforce a reasonable class name length so the path is not too long for PLATFORM_MAX_FILEPATH_LENGTH
2014-04-23 18:13:40 -04:00
namespace ContentBrowserUtils
{
// Keep a map of all the paths that have custom colors, so updating the color in one location updates them all
static TMap < FString , TSharedPtr < FLinearColor > > PathColors ;
}
2014-03-14 14:13:41 -04:00
class SContentBrowserPopup : public SCompoundWidget
{
public :
SLATE_BEGIN_ARGS ( SContentBrowserPopup ) { }
SLATE_ATTRIBUTE ( FText , Message )
SLATE_END_ARGS ( )
/** Constructs this widget with InArgs */
BEGIN_SLATE_FUNCTION_BUILD_OPTIMIZATION
void Construct ( const FArguments & InArgs )
{
ChildSlot
[
SNew ( SBorder )
. BorderImage ( FEditorStyle : : GetBrush ( " Menu.Background " ) )
. Padding ( 10 )
. OnMouseButtonDown ( this , & SContentBrowserPopup : : OnBorderClicked )
. BorderBackgroundColor ( this , & SContentBrowserPopup : : GetBorderBackgroundColor )
[
SNew ( SHorizontalBox )
+ SHorizontalBox : : Slot ( )
. AutoWidth ( )
. VAlign ( VAlign_Center )
. Padding ( 0 , 0 , 4 , 0 )
[
SNew ( SImage ) . Image ( FEditorStyle : : GetBrush ( " ContentBrowser.PopupMessageIcon " ) )
]
+ SHorizontalBox : : Slot ( )
. AutoWidth ( )
. VAlign ( VAlign_Center )
[
SNew ( STextBlock )
. Text ( InArgs . _Message )
. WrapTextAt ( 450 )
]
]
] ;
}
END_SLATE_FUNCTION_BUILD_OPTIMIZATION
static void DisplayMessage ( const FText & Message , const FSlateRect & ScreenAnchor , TSharedRef < SWidget > ParentContent )
{
TSharedRef < SContentBrowserPopup > PopupContent = SNew ( SContentBrowserPopup ) . Message ( Message ) ;
const FVector2D ScreenLocation = FVector2D ( ScreenAnchor . Left , ScreenAnchor . Top ) ;
const bool bFocusImmediately = true ;
const bool bShouldAutoSize = true ;
const FVector2D WindowSize = FVector2D : : ZeroVector ;
const FVector2D SummonLocationSize = ScreenAnchor . GetSize ( ) ;
TSharedRef < SWindow > PopupWindow = FSlateApplication : : Get ( ) . PushMenu (
ParentContent ,
PopupContent ,
ScreenLocation ,
FPopupTransitionEffect ( FPopupTransitionEffect : : TopMenu ) ,
bFocusImmediately ,
bShouldAutoSize ,
WindowSize ,
SummonLocationSize
) ;
PopupContent - > SetWindow ( PopupWindow ) ;
}
private :
void SetWindow ( const TSharedRef < SWindow > & InWindow )
{
Window = InWindow ;
}
FReply OnBorderClicked ( const FGeometry & Geometry , const FPointerEvent & MouseEvent )
{
if ( Window . IsValid ( ) )
{
Window . Pin ( ) - > RequestDestroyWindow ( ) ;
}
return FReply : : Handled ( ) ;
}
FSlateColor GetBorderBackgroundColor ( ) const
{
return IsHovered ( ) ? FLinearColor ( 0.5 , 0.5 , 0.5 , 1 ) : FLinearColor : : White ;
}
private :
TWeakPtr < SWindow > Window ;
} ;
/** A miniture confirmation popup for quick yes/no questions */
class SContentBrowserConfirmPopup : public SCompoundWidget
{
public :
SLATE_BEGIN_ARGS ( SContentBrowserConfirmPopup ) { }
/** The text to display */
SLATE_ARGUMENT ( FText , Prompt )
/** The Yes Button to display */
SLATE_ARGUMENT ( FText , YesText )
/** The No Button to display */
SLATE_ARGUMENT ( FText , NoText )
/** Invoked when yes is clicked */
SLATE_EVENT ( FOnClicked , OnYesClicked )
/** Invoked when no is clicked */
SLATE_EVENT ( FOnClicked , OnNoClicked )
SLATE_END_ARGS ( )
BEGIN_SLATE_FUNCTION_BUILD_OPTIMIZATION
void Construct ( const FArguments & InArgs )
{
OnYesClicked = InArgs . _OnYesClicked ;
OnNoClicked = InArgs . _OnNoClicked ;
ChildSlot
[
SNew ( SBorder )
. BorderImage ( FEditorStyle : : GetBrush ( " Menu.Background " ) )
. Padding ( 10 )
[
SNew ( SVerticalBox )
+ SVerticalBox : : Slot ( )
. AutoHeight ( )
. Padding ( 0 , 0 , 0 , 5 )
. HAlign ( HAlign_Center )
[
SNew ( STextBlock )
. Text ( InArgs . _Prompt )
]
+ SVerticalBox : : Slot ( )
. AutoHeight ( )
. HAlign ( HAlign_Center )
[
SNew ( SUniformGridPanel )
. SlotPadding ( 3 )
+ SUniformGridPanel : : Slot ( 0 , 0 )
. HAlign ( HAlign_Fill )
[
SNew ( SButton )
. HAlign ( HAlign_Center )
. Text ( InArgs . _YesText )
. OnClicked ( this , & SContentBrowserConfirmPopup : : YesClicked )
]
+ SUniformGridPanel : : Slot ( 1 , 0 )
. HAlign ( HAlign_Fill )
[
SNew ( SButton )
. HAlign ( HAlign_Center )
. Text ( InArgs . _NoText )
. OnClicked ( this , & SContentBrowserConfirmPopup : : NoClicked )
]
]
]
] ;
}
END_SLATE_FUNCTION_BUILD_OPTIMIZATION
/** Opens the popup using the specified component as its parent */
void OpenPopup ( const TSharedRef < SWidget > & ParentContent )
{
// Show dialog to confirm the delete
PopupWindow = FSlateApplication : : Get ( ) . PushMenu (
ParentContent ,
SharedThis ( this ) ,
FSlateApplication : : Get ( ) . GetCursorPos ( ) ,
FPopupTransitionEffect ( FPopupTransitionEffect : : TopMenu )
) ;
}
private :
/** The yes button was clicked */
FReply YesClicked ( )
{
if ( OnYesClicked . IsBound ( ) )
{
OnYesClicked . Execute ( ) ;
}
PopupWindow . Pin ( ) - > RequestDestroyWindow ( ) ;
return FReply : : Handled ( ) ;
}
/** The no button was clicked */
FReply NoClicked ( )
{
if ( OnNoClicked . IsBound ( ) )
{
OnNoClicked . Execute ( ) ;
}
PopupWindow . Pin ( ) - > RequestDestroyWindow ( ) ;
return FReply : : Handled ( ) ;
}
/** The window containing this popup */
TWeakPtr < SWindow > PopupWindow ;
/** Delegates for button clicks */
FOnClicked OnYesClicked ;
FOnClicked OnNoClicked ;
} ;
bool ContentBrowserUtils : : OpenEditorForAsset ( const FString & ObjectPath )
{
// Load the asset if unloaded
TArray < UObject * > LoadedObjects ;
TArray < FString > ObjectPaths ;
ObjectPaths . Add ( ObjectPath ) ;
ContentBrowserUtils : : LoadAssetsIfNeeded ( ObjectPaths , LoadedObjects ) ;
// Open the editor for the specified asset
UObject * FoundObject = FindObject < UObject > ( NULL , * ObjectPath ) ;
return OpenEditorForAsset ( FoundObject ) ;
}
bool ContentBrowserUtils : : OpenEditorForAsset ( UObject * Asset )
{
if ( Asset ! = NULL )
{
// @todo toolkit minor: Needs world-centric support?
return FAssetEditorManager : : Get ( ) . OpenEditorForAsset ( Asset ) ;
}
return false ;
}
bool ContentBrowserUtils : : OpenEditorForAsset ( const TArray < UObject * > & Assets )
{
if ( Assets . Num ( ) = = 1 )
{
return OpenEditorForAsset ( Assets [ 0 ] ) ;
}
else if ( Assets . Num ( ) > 1 )
{
return FAssetEditorManager : : Get ( ) . OpenEditorForAssets ( Assets ) ;
}
return false ;
}
bool ContentBrowserUtils : : LoadAssetsIfNeeded ( const TArray < FString > & ObjectPaths , TArray < UObject * > & LoadedObjects , bool bAllowedToPromptToLoadAssets )
{
bool bAnyObjectsWereLoadedOrUpdated = false ;
// Build a list of unloaded assets
TArray < FString > UnloadedObjectPaths ;
2014-05-14 22:02:10 -04:00
bool bAtLeastOneUnloadedMap = false ;
2014-03-14 14:13:41 -04:00
for ( int32 PathIdx = 0 ; PathIdx < ObjectPaths . Num ( ) ; + + PathIdx )
{
const FString & ObjectPath = ObjectPaths [ PathIdx ] ;
2014-05-14 22:02:10 -04:00
UObject * FoundObject = FindObject < UObject > ( NULL , * ObjectPath ) ;
if ( FoundObject )
2014-03-14 14:13:41 -04:00
{
2014-05-14 22:02:10 -04:00
LoadedObjects . Add ( FoundObject ) ;
}
else
{
// Unloaded asset, we will load it later
UnloadedObjectPaths . Add ( ObjectPath ) ;
if ( FEditorFileUtils : : IsMapPackageAsset ( ObjectPath ) )
2014-03-14 14:13:41 -04:00
{
2014-05-14 22:02:10 -04:00
bAtLeastOneUnloadedMap = true ;
2014-03-14 14:13:41 -04:00
}
}
}
// if we are allowed to prompt the user to load and we have enough assets that requires prompting then we should
// prompt and load assets if the user said it was ok
bool bShouldLoadAssets = true ;
if ( bAllowedToPromptToLoadAssets & & ShouldPromptToLoadAssets ( ObjectPaths , UnloadedObjectPaths ) )
{
bShouldLoadAssets = PromptToLoadAssets ( ObjectPaths ) ;
}
// Ask for confirmation if the user is attempting to load a large number of assets
if ( bShouldLoadAssets = = false )
{
return false ;
}
// Make sure all selected objects are loaded, where possible
if ( UnloadedObjectPaths . Num ( ) > 0 )
{
// Get the maximum objects to load before displaying the slow task
2014-05-14 22:02:10 -04:00
const bool bShowProgressDialog = ( UnloadedObjectPaths . Num ( ) > GetDefault < UContentBrowserSettings > ( ) - > NumObjectsToLoadBeforeWarning ) | | bAtLeastOneUnloadedMap ;
2014-03-14 14:13:41 -04:00
GWarn - > BeginSlowTask ( LOCTEXT ( " LoadingObjects " , " Loading Objects... " ) , bShowProgressDialog ) ;
GIsEditorLoadingPackage = true ;
2014-04-02 18:09:23 -04:00
bool bSomeObjectsFailedToLoad = false ;
2014-03-14 14:13:41 -04:00
for ( int32 PathIdx = 0 ; PathIdx < UnloadedObjectPaths . Num ( ) ; + + PathIdx )
{
const FString & ObjectPath = UnloadedObjectPaths [ PathIdx ] ;
// We never want to follow redirects when loading objects for the Content Browser. It would
// allow a user to interact with a ghost/unverified asset as if it were still alive.
const ELoadFlags LoadFlags = LOAD_NoRedirects ;
// Load up the object
UObject * LoadedObject = LoadObject < UObject > ( NULL , * ObjectPath , NULL , LoadFlags , NULL ) ;
if ( LoadedObject )
{
LoadedObjects . Add ( LoadedObject ) ;
}
else
{
2014-04-02 18:09:23 -04:00
bSomeObjectsFailedToLoad = true ;
2014-03-14 14:13:41 -04:00
}
if ( bShowProgressDialog )
{
GWarn - > UpdateProgress ( PathIdx , UnloadedObjectPaths . Num ( ) ) ;
}
if ( GWarn - > ReceivedUserCancel ( ) )
{
// If the user has canceled stop loading the remaining objects. We don't add the remaining objects to the failed string,
// this would only result in launching another dialog when by their actions the user clearly knows not all of the
// assets will have been loaded.
break ;
}
}
GIsEditorLoadingPackage = false ;
GWarn - > EndSlowTask ( ) ;
2014-04-02 18:09:23 -04:00
if ( bSomeObjectsFailedToLoad )
2014-03-14 14:13:41 -04:00
{
2014-04-02 18:09:23 -04:00
FNotificationInfo Info ( LOCTEXT ( " LoadObjectFailed " , " Failed to load assets " ) ) ;
Info . ExpireDuration = 5.0f ;
Info . Hyperlink = FSimpleDelegate : : CreateStatic ( [ ] ( ) { FMessageLog ( " LoadErrors " ) . Open ( EMessageSeverity : : Info , true ) ; } ) ;
2014-06-17 12:05:50 -04:00
Info . HyperlinkText = LOCTEXT ( " LoadObjectHyperlink " , " Show Message Log " ) ;
2014-04-02 18:09:23 -04:00
FSlateNotificationManager : : Get ( ) . AddNotification ( Info ) ;
2014-03-14 14:13:41 -04:00
return false ;
}
}
return true ;
}
bool ContentBrowserUtils : : ShouldPromptToLoadAssets ( const TArray < FString > & ObjectPaths , TArray < FString > & OutUnloadedObjects )
{
OutUnloadedObjects . Empty ( ) ;
bool bShouldPrompt = false ;
// Build a list of unloaded assets
for ( int32 PathIdx = 0 ; PathIdx < ObjectPaths . Num ( ) ; + + PathIdx )
{
const FString & ObjectPath = ObjectPaths [ PathIdx ] ;
2014-05-14 22:02:10 -04:00
UObject * FoundObject = FindObject < UObject > ( NULL , * ObjectPath ) ;
if ( ! FoundObject )
2014-03-14 14:13:41 -04:00
{
2014-05-14 22:02:10 -04:00
// Unloaded asset, we will load it later
OutUnloadedObjects . Add ( ObjectPath ) ;
2014-03-14 14:13:41 -04:00
}
}
// Get the maximum objects to load before displaying a warning
// Ask for confirmation if the user is attempting to load a large number of assets
if ( OutUnloadedObjects . Num ( ) > GetDefault < UContentBrowserSettings > ( ) - > NumObjectsToLoadBeforeWarning )
{
bShouldPrompt = true ;
}
return bShouldPrompt ;
}
bool ContentBrowserUtils : : PromptToLoadAssets ( const TArray < FString > & UnloadedObjects )
{
bool bShouldLoadAssets = false ;
// Prompt the user to load assets
const FText Question = FText : : Format ( LOCTEXT ( " ConfirmLoadAssets " , " You are about to load {0} assets. Would you like to proceed? " ) , FText : : AsNumber ( UnloadedObjects . Num ( ) ) ) ;
if ( EAppReturnType : : Yes = = FMessageDialog : : Open ( EAppMsgType : : YesNo , Question ) )
{
bShouldLoadAssets = true ;
}
return bShouldLoadAssets ;
}
void ContentBrowserUtils : : RenameAsset ( UObject * Asset , const FString & NewName , FText & ErrorMessage )
{
FAssetToolsModule & AssetToolsModule = FModuleManager : : LoadModuleChecked < FAssetToolsModule > ( " AssetTools " ) ;
TArray < FAssetRenameData > AssetsAndNames ;
const FString PackagePath = FPackageName : : GetLongPackagePath ( Asset - > GetOutermost ( ) - > GetName ( ) ) ;
new ( AssetsAndNames ) FAssetRenameData ( Asset , PackagePath , NewName ) ;
AssetToolsModule . Get ( ) . RenameAssets ( AssetsAndNames ) ;
}
2014-07-09 06:31:13 -04:00
void ContentBrowserUtils : : CopyAssets ( const TArray < UObject * > & Assets , const FString & DestPath )
{
TArray < UObject * > NewObjects ;
ObjectTools : : DuplicateObjects ( Assets , TEXT ( " " ) , DestPath , /*bOpenDialog=*/ false , & NewObjects ) ;
// If any objects were duplicated, report the success
if ( NewObjects . Num ( ) )
{
FFormatNamedArguments Args ;
Args . Add ( TEXT ( " Number " ) , NewObjects . Num ( ) ) ;
const FText Message = FText : : Format ( LOCTEXT ( " AssetsDroppedCopy " , " {Number} asset(s) copied " ) , Args ) ;
FSlateNotificationManager : : Get ( ) . AddNotification ( FNotificationInfo ( Message ) ) ;
// Now branch the files in source control if possible
check ( Assets . Num ( ) = = NewObjects . Num ( ) ) ;
for ( int32 ObjectIndex = 0 ; ObjectIndex < Assets . Num ( ) ; ObjectIndex + + )
{
UObject * SourceAsset = Assets [ ObjectIndex ] ;
UObject * DestAsset = NewObjects [ ObjectIndex ] ;
SourceControlHelpers : : BranchPackage ( DestAsset - > GetOutermost ( ) , SourceAsset - > GetOutermost ( ) ) ;
}
}
}
2014-03-14 14:13:41 -04:00
void ContentBrowserUtils : : MoveAssets ( const TArray < UObject * > & Assets , const FString & DestPath , const FString & SourcePath )
{
check ( DestPath . Len ( ) > 0 ) ;
FAssetToolsModule & AssetToolsModule = FModuleManager : : LoadModuleChecked < FAssetToolsModule > ( " AssetTools " ) ;
TArray < FAssetRenameData > AssetsAndNames ;
for ( auto AssetIt = Assets . CreateConstIterator ( ) ; AssetIt ; + + AssetIt )
{
UObject * Asset = * AssetIt ;
if ( ! ensure ( Asset ) )
{
continue ;
}
FString PackagePath ;
FString ObjectName = Asset - > GetName ( ) ;
if ( SourcePath . Len ( ) )
{
const FString CurrentPackageName = Asset - > GetOutermost ( ) - > GetName ( ) ;
// This is a relative operation
if ( ! ensure ( CurrentPackageName . StartsWith ( SourcePath ) ) )
{
continue ;
}
// Collect the relative path then use it to determine the new location
// For example, if SourcePath = /Game/MyPath and CurrentPackageName = /Game/MyPath/MySubPath/MyAsset
// /Game/MyPath/MySubPath/MyAsset -> /MySubPath
const int32 ShortPackageNameLen = FPackageName : : GetLongPackageAssetName ( CurrentPackageName ) . Len ( ) ;
const int32 RelativePathLen = CurrentPackageName . Len ( ) - ShortPackageNameLen - SourcePath . Len ( ) - 1 ; // -1 to exclude the trailing "/"
const FString RelativeDestPath = CurrentPackageName . Mid ( SourcePath . Len ( ) , RelativePathLen ) ;
PackagePath = DestPath + RelativeDestPath ;
}
else
{
// Only a DestPath was supplied, use it
PackagePath = DestPath ;
}
new ( AssetsAndNames ) FAssetRenameData ( Asset , PackagePath , ObjectName ) ;
}
if ( AssetsAndNames . Num ( ) > 0 )
{
AssetToolsModule . Get ( ) . RenameAssets ( AssetsAndNames ) ;
}
2014-07-09 06:31:13 -04:00
// Now branch the files in source control if possible
for ( const auto & AssetAndName : AssetsAndNames )
{
check ( AssetAndName . Asset . Get ( ) ) ;
UPackage * DestPackage = FindPackage ( nullptr , * ( AssetAndName . PackagePath / AssetAndName . NewName ) ) ;
UPackage * SourcePackage = FindPackage ( nullptr , * AssetAndName . OriginalAssetPath ) ;
check ( DestPackage ) ;
check ( SourcePackage ) ;
SourceControlHelpers : : BranchPackage ( DestPackage , SourcePackage ) ;
}
2014-03-14 14:13:41 -04:00
}
int32 ContentBrowserUtils : : DeleteAssets ( const TArray < UObject * > & AssetsToDelete )
{
return ObjectTools : : DeleteObjects ( AssetsToDelete ) ;
}
bool ContentBrowserUtils : : DeleteFolders ( const TArray < FString > & PathsToDelete )
{
// Get a list of assets in the paths to delete
TArray < FAssetData > AssetDataList ;
GetAssetsInPaths ( PathsToDelete , AssetDataList ) ;
const int32 NumAssetsInPaths = AssetDataList . Num ( ) ;
bool bAllowFolderDelete = false ;
if ( NumAssetsInPaths = = 0 )
{
// There were no assets, allow the folder delete.
bAllowFolderDelete = true ;
}
else
{
// Load all the assets in the folder and attempt to delete them.
// If it was successful, allow the folder delete.
// Get a list of object paths for input into LoadAssetsIfNeeded
TArray < FString > ObjectPaths ;
for ( auto AssetIt = AssetDataList . CreateConstIterator ( ) ; AssetIt ; + + AssetIt )
{
ObjectPaths . Add ( ( * AssetIt ) . ObjectPath . ToString ( ) ) ;
}
// Load all the assets in the selected paths
TArray < UObject * > LoadedAssets ;
if ( ContentBrowserUtils : : LoadAssetsIfNeeded ( ObjectPaths , LoadedAssets ) )
{
// Make sure we loaded all of them
if ( LoadedAssets . Num ( ) = = NumAssetsInPaths )
{
const int32 NumAssetsDeleted = ContentBrowserUtils : : DeleteAssets ( LoadedAssets ) ;
if ( NumAssetsDeleted = = NumAssetsInPaths )
{
// Successfully deleted all assets in the specified path. Allow the folder to be removed.
bAllowFolderDelete = true ;
}
else
{
// Not all the assets in the selected paths were deleted
}
}
else
{
// Not all the assets in the selected paths were loaded
}
}
else
{
// The user declined to load some assets or some assets failed to load
}
}
if ( bAllowFolderDelete )
{
FAssetRegistryModule & AssetRegistryModule = FModuleManager : : LoadModuleChecked < FAssetRegistryModule > ( " AssetRegistry " ) ;
for ( auto PathIt = PathsToDelete . CreateConstIterator ( ) ; PathIt ; + + PathIt )
{
AssetRegistryModule . Get ( ) . RemovePath ( * PathIt ) ;
}
return true ;
}
return false ;
}
void ContentBrowserUtils : : GetAssetsInPaths ( const TArray < FString > & InPaths , TArray < FAssetData > & OutAssetDataList )
{
// Load the asset registry module
FAssetRegistryModule & AssetRegistryModule = FModuleManager : : Get ( ) . LoadModuleChecked < FAssetRegistryModule > ( TEXT ( " AssetRegistry " ) ) ;
// Form a filter from the paths
FARFilter Filter ;
Filter . bRecursivePaths = true ;
for ( int32 PathIdx = 0 ; PathIdx < InPaths . Num ( ) ; + + PathIdx )
{
new ( Filter . PackagePaths ) FName ( * InPaths [ PathIdx ] ) ;
}
// Query for a list of assets in the selected paths
AssetRegistryModule . Get ( ) . GetAssets ( Filter , OutAssetDataList ) ;
}
bool ContentBrowserUtils : : SavePackages ( const TArray < UPackage * > & Packages )
{
TArray < UPackage * > PackagesWithExternalRefs ;
FString PackageNames ;
if ( PackageTools : : CheckForReferencesToExternalPackages ( & Packages , & PackagesWithExternalRefs ) )
{
for ( int32 PkgIdx = 0 ; PkgIdx < PackagesWithExternalRefs . Num ( ) ; + + PkgIdx )
{
PackageNames + = FString : : Printf ( TEXT ( " %s \n " ) , * PackagesWithExternalRefs [ PkgIdx ] - > GetName ( ) ) ;
}
bool bProceed = EAppReturnType : : Yes = = FMessageDialog : : Open (
EAppMsgType : : YesNo ,
FText : : Format (
NSLOCTEXT ( " UnrealEd " , " Warning_ExternalPackageRef " , " The following assets have references to external assets: \n {0} \n External assets won't be found when in a game and all references will be broken. Proceed? " ) ,
FText : : FromString ( PackageNames ) ) ) ;
if ( ! bProceed )
{
return false ;
}
}
const bool bCheckDirty = false ;
const bool bPromptToSave = false ;
const FEditorFileUtils : : EPromptReturnCode Return = FEditorFileUtils : : PromptForCheckoutAndSave ( Packages , bCheckDirty , bPromptToSave ) ;
return Return = = FEditorFileUtils : : EPromptReturnCode : : PR_Success ;
}
bool ContentBrowserUtils : : SaveDirtyPackages ( )
{
const bool bPromptUserToSave = true ;
2014-09-15 21:53:58 -04:00
const bool bSaveMapPackages = true ;
2014-03-14 14:13:41 -04:00
const bool bSaveContentPackages = true ;
return FEditorFileUtils : : SaveDirtyPackages ( bPromptUserToSave , bSaveMapPackages , bSaveContentPackages ) ;
}
TArray < UPackage * > ContentBrowserUtils : : LoadPackages ( const TArray < FString > & PackageNames )
{
TArray < UPackage * > LoadedPackages ;
GWarn - > BeginSlowTask ( LOCTEXT ( " LoadingPackages " , " Loading Packages... " ) , true ) ;
for ( int32 PackageIdx = 0 ; PackageIdx < PackageNames . Num ( ) ; + + PackageIdx )
{
const FString & PackageName = PackageNames [ PackageIdx ] ;
if ( ! ensure ( PackageName . Len ( ) > 0 ) )
{
// Empty package name. Skip it.
continue ;
}
UPackage * Package = FindPackage ( NULL , * PackageName ) ;
if ( Package ! = NULL )
{
// The package is at least partially loaded. Fully load it.
Package - > FullyLoad ( ) ;
}
else
{
// The package is unloaded. Try to load the package from disk.
Package = PackageTools : : LoadPackage ( PackageName ) ;
}
// If the package was loaded, add it to the loaded packages list.
if ( Package ! = NULL )
{
LoadedPackages . Add ( Package ) ;
}
}
GWarn - > EndSlowTask ( ) ;
return LoadedPackages ;
}
void ContentBrowserUtils : : DisplayMessage ( const FText & Message , const FSlateRect & ScreenAnchor , const TSharedRef < SWidget > & ParentContent )
{
SContentBrowserPopup : : DisplayMessage ( Message , ScreenAnchor , ParentContent ) ;
}
void ContentBrowserUtils : : DisplayConfirmationPopup ( const FText & Message , const FText & YesString , const FText & NoString , const TSharedRef < SWidget > & ParentContent , const FOnClicked & OnYesClicked , const FOnClicked & OnNoClicked )
{
TSharedRef < SContentBrowserConfirmPopup > Popup =
SNew ( SContentBrowserConfirmPopup )
. Prompt ( Message )
. YesText ( YesString )
. NoText ( NoString )
. OnYesClicked ( OnYesClicked )
. OnNoClicked ( OnNoClicked ) ;
Popup - > OpenPopup ( ParentContent ) ;
}
void ContentBrowserUtils : : CopyFolders ( const TArray < FString > & InSourcePathNames , const FString & DestPath )
{
TMap < FString , TArray < UObject * > > SourcePathToLoadedAssets ;
// Make sure the destination path is not in the source path list
TArray < FString > SourcePathNames = InSourcePathNames ;
SourcePathNames . Remove ( DestPath ) ;
// Load all assets in the source paths
PrepareFoldersForDragDrop ( SourcePathNames , SourcePathToLoadedAssets ) ;
// Load the Asset Registry to update paths during the copy
FAssetRegistryModule & AssetRegistryModule = FModuleManager : : LoadModuleChecked < FAssetRegistryModule > ( " AssetRegistry " ) ;
// For every path which contained valid assets...
for ( auto PathIt = SourcePathToLoadedAssets . CreateConstIterator ( ) ; PathIt ; + + PathIt )
{
// Put dragged folders in a sub-folder under the destination path
FString SubFolderName = FPackageName : : GetLongPackageAssetName ( PathIt . Key ( ) ) ;
FString Destination = DestPath + TEXT ( " / " ) + SubFolderName ;
// Add the new path to notify sources views
AssetRegistryModule . Get ( ) . AddPath ( Destination ) ;
// If any assets were in this path...
if ( PathIt . Value ( ) . Num ( ) > 0 )
{
// Copy assets and supply a source path to indicate it is relative
ObjectTools : : DuplicateObjects ( PathIt . Value ( ) , PathIt . Key ( ) , Destination , /*bOpenDialog=*/ false ) ;
}
}
}
void ContentBrowserUtils : : MoveFolders ( const TArray < FString > & InSourcePathNames , const FString & DestPath )
{
TMap < FString , TArray < UObject * > > SourcePathToLoadedAssets ;
2014-07-07 11:16:23 -04:00
// Do not allow parent directories to be moved to themselves or children.
2014-03-14 14:13:41 -04:00
TArray < FString > SourcePathNames = InSourcePathNames ;
2014-07-07 11:16:23 -04:00
TArray < FString > SourcePathNamesToRemove ;
for ( auto SourcePathIt = SourcePathNames . CreateConstIterator ( ) ; SourcePathIt ; + + SourcePathIt )
{
if ( DestPath . StartsWith ( * SourcePathIt ) )
{
SourcePathNamesToRemove . Add ( * SourcePathIt ) ;
}
}
for ( auto SourcePathToRemoveIt = SourcePathNamesToRemove . CreateConstIterator ( ) ; SourcePathToRemoveIt ; + + SourcePathToRemoveIt )
{
SourcePathNames . Remove ( * SourcePathToRemoveIt ) ;
}
2014-03-14 14:13:41 -04:00
// Load all assets in the source paths
PrepareFoldersForDragDrop ( SourcePathNames , SourcePathToLoadedAssets ) ;
// Load the Asset Registry to update paths during the move
FAssetRegistryModule & AssetRegistryModule = FModuleManager : : LoadModuleChecked < FAssetRegistryModule > ( " AssetRegistry " ) ;
// For every path which contained valid assets...
for ( auto PathIt = SourcePathToLoadedAssets . CreateConstIterator ( ) ; PathIt ; + + PathIt )
{
// Put dragged folders in a sub-folder under the destination path
const FString SourcePath = PathIt . Key ( ) ;
const FString SubFolderName = FPackageName : : GetLongPackageAssetName ( SourcePath ) ;
const FString Destination = DestPath + TEXT ( " / " ) + SubFolderName ;
// Add the new path to notify sources views
AssetRegistryModule . Get ( ) . AddPath ( Destination ) ;
// If any assets were in this path...
if ( PathIt . Value ( ) . Num ( ) > 0 )
{
// Move assets and supply a source path to indicate it is relative
ContentBrowserUtils : : MoveAssets ( PathIt . Value ( ) , Destination , PathIt . Key ( ) ) ;
}
// Attempt to remove the old paths. This operation will silently fail if any assets failed to move.
AssetRegistryModule . Get ( ) . RemovePath ( SourcePath ) ;
}
}
void ContentBrowserUtils : : PrepareFoldersForDragDrop ( const TArray < FString > & SourcePathNames , TMap < FString , TArray < UObject * > > & OutSourcePathToLoadedAssets )
{
TSet < UObject * > AllFoundObjects ;
GWarn - > BeginSlowTask ( LOCTEXT ( " FolderDragDrop_Loading " , " Loading folders " ) , true ) ;
// Load the Asset Registry to update paths during the move
FAssetRegistryModule & AssetRegistryModule = FModuleManager : : LoadModuleChecked < FAssetRegistryModule > ( " AssetRegistry " ) ;
2014-04-02 18:09:23 -04:00
// Check up-front how many assets we might load in this operation & warn the user
TArray < FString > ObjectPathsToWarnAbout ;
for ( auto PathIt = SourcePathNames . CreateConstIterator ( ) ; PathIt ; + + PathIt )
{
// Get all assets in this path
TArray < FAssetData > AssetDataList ;
AssetRegistryModule . Get ( ) . GetAssetsByPath ( FName ( * * PathIt ) , AssetDataList , true ) ;
for ( auto AssetIt = AssetDataList . CreateConstIterator ( ) ; AssetIt ; + + AssetIt )
{
ObjectPathsToWarnAbout . Add ( ( * AssetIt ) . ObjectPath . ToString ( ) ) ;
}
}
TArray < FString > UnloadedObjects ;
if ( ShouldPromptToLoadAssets ( ObjectPathsToWarnAbout , UnloadedObjects ) )
{
PromptToLoadAssets ( UnloadedObjects ) ;
}
2014-03-14 14:13:41 -04:00
// For every source path, load every package in the path (if necessary) and keep track of the assets that were loaded
for ( auto PathIt = SourcePathNames . CreateConstIterator ( ) ; PathIt ; + + PathIt )
{
// Get all assets in this path
TArray < FAssetData > AssetDataList ;
AssetRegistryModule . Get ( ) . GetAssetsByPath ( FName ( * * PathIt ) , AssetDataList , true ) ;
// Form a list of all object paths for these assets
TArray < FString > ObjectPaths ;
for ( auto AssetIt = AssetDataList . CreateConstIterator ( ) ; AssetIt ; + + AssetIt )
{
ObjectPaths . Add ( ( * AssetIt ) . ObjectPath . ToString ( ) ) ;
}
// Load all assets in this path if needed
TArray < UObject * > AllLoadedAssets ;
2014-04-02 18:09:23 -04:00
LoadAssetsIfNeeded ( ObjectPaths , AllLoadedAssets , false ) ;
2014-03-14 14:13:41 -04:00
// Add a slash to the end of the path so StartsWith doesn't get a false positive on similarly named folders
const FString SourcePathWithSlash = * PathIt + TEXT ( " / " ) ;
2014-07-07 11:16:23 -04:00
// Find all files in this path and subpaths
TArray < FString > Filenames ;
FString RootFolder = FPackageName : : LongPackageNameToFilename ( SourcePathWithSlash ) ;
FPackageName : : FindPackagesInDirectory ( Filenames , RootFolder ) ;
2014-03-14 14:13:41 -04:00
// Now find all assets in memory that were loaded from this path that are valid for drag-droppping
TArray < UObject * > ValidLoadedAssets ;
for ( auto AssetIt = AllLoadedAssets . CreateConstIterator ( ) ; AssetIt ; + + AssetIt )
{
UObject * Asset = * AssetIt ;
if ( ( Asset - > GetClass ( ) ! = UObjectRedirector : : StaticClass ( ) & & // Skip object redirectors
! Asset - > GetOutermost ( ) - > ContainsMap ( ) & & // Skip assets in maps
! AllFoundObjects . Contains ( Asset ) // Skip assets we have already found to avoid processing them twice
) )
{
ValidLoadedAssets . Add ( Asset ) ;
AllFoundObjects . Add ( Asset ) ;
}
}
// Add an entry of the map of source paths to assets found, whether any assets were found or not
OutSourcePathToLoadedAssets . Add ( * PathIt , ValidLoadedAssets ) ;
}
GWarn - > EndSlowTask ( ) ;
ensure ( SourcePathNames . Num ( ) = = OutSourcePathToLoadedAssets . Num ( ) ) ;
}
void ContentBrowserUtils : : CopyAssetReferencesToClipboard ( const TArray < FAssetData > & AssetsToCopy )
{
FString ClipboardText ;
for ( auto AssetIt = AssetsToCopy . CreateConstIterator ( ) ; AssetIt ; + + AssetIt )
{
if ( ClipboardText . Len ( ) > 0 )
{
ClipboardText + = LINE_TERMINATOR ;
}
ClipboardText + = ( * AssetIt ) . GetExportTextName ( ) ;
}
FPlatformMisc : : ClipboardCopy ( * ClipboardText ) ;
}
void ContentBrowserUtils : : CaptureThumbnailFromViewport ( FViewport * InViewport , const TArray < FAssetData > & InAssetsToAssign )
{
//capture the thumbnail
uint32 SrcWidth = InViewport - > GetSizeXY ( ) . X ;
uint32 SrcHeight = InViewport - > GetSizeXY ( ) . Y ;
// Read the contents of the viewport into an array.
TArray < FColor > OrigBitmap ;
if ( InViewport - > ReadPixels ( OrigBitmap ) )
{
check ( OrigBitmap . Num ( ) = = SrcWidth * SrcHeight ) ;
//pin to smallest value
int32 CropSize = FMath : : Min < uint32 > ( SrcWidth , SrcHeight ) ;
//pin to max size
int32 ScaledSize = FMath : : Min < uint32 > ( ThumbnailTools : : DefaultThumbnailSize , CropSize ) ;
//calculations for cropping
TArray < FColor > CroppedBitmap ;
CroppedBitmap . AddUninitialized ( CropSize * CropSize ) ;
//Crop the image
int32 CroppedSrcTop = ( SrcHeight - CropSize ) / 2 ;
int32 CroppedSrcLeft = ( SrcWidth - CropSize ) / 2 ;
for ( int32 Row = 0 ; Row < CropSize ; + + Row )
{
//Row*Side of a row*byte per color
int32 SrcPixelIndex = ( CroppedSrcTop + Row ) * SrcWidth + CroppedSrcLeft ;
const void * SrcPtr = & ( OrigBitmap [ SrcPixelIndex ] ) ;
void * DstPtr = & ( CroppedBitmap [ Row * CropSize ] ) ;
FMemory : : Memcpy ( DstPtr , SrcPtr , CropSize * 4 ) ;
}
//Scale image down if needed
TArray < FColor > ScaledBitmap ;
if ( ScaledSize < CropSize )
{
FImageUtils : : ImageResize ( CropSize , CropSize , CroppedBitmap , ScaledSize , ScaledSize , ScaledBitmap , true ) ;
}
else
{
//just copy the data over. sizes are the same
ScaledBitmap = CroppedBitmap ;
}
//setup actual thumbnail
FObjectThumbnail TempThumbnail ;
TempThumbnail . SetImageSize ( ScaledSize , ScaledSize ) ;
TArray < uint8 > & ThumbnailByteArray = TempThumbnail . AccessImageData ( ) ;
// Copy scaled image into destination thumb
int32 MemorySize = ScaledSize * ScaledSize * sizeof ( FColor ) ;
ThumbnailByteArray . AddUninitialized ( MemorySize ) ;
FMemory : : Memcpy ( & ( ThumbnailByteArray [ 0 ] ) , & ( ScaledBitmap [ 0 ] ) , MemorySize ) ;
FAssetToolsModule & AssetToolsModule = FModuleManager : : LoadModuleChecked < FAssetToolsModule > ( " AssetTools " ) ;
//check if each asset should receive the new thumb nail
for ( auto AssetIt = InAssetsToAssign . CreateConstIterator ( ) ; AssetIt ; + + AssetIt )
{
const FAssetData & CurrentAsset = * AssetIt ;
// check whether this is a type that uses one of the shared static thumbnails
if ( AssetToolsModule . Get ( ) . AssetUsesGenericThumbnail ( CurrentAsset ) )
{
//assign the thumbnail and dirty
const FString ObjectFullName = CurrentAsset . GetFullName ( ) ;
const FString PackageName = CurrentAsset . PackageName . ToString ( ) ;
UPackage * AssetPackage = FindObject < UPackage > ( NULL , * PackageName ) ;
if ( ensure ( AssetPackage ) )
{
FObjectThumbnail * NewThumbnail = ThumbnailTools : : CacheThumbnail ( ObjectFullName , & TempThumbnail , AssetPackage ) ;
if ( ensure ( NewThumbnail ) )
{
//we need to indicate that the package needs to be resaved
AssetPackage - > MarkPackageDirty ( ) ;
// Let the content browser know that we've changed the thumbnail
NewThumbnail - > MarkAsDirty ( ) ;
// Signal that the asset was changed if it is loaded so thumbnail pools will update
if ( CurrentAsset . IsAssetLoaded ( ) )
{
CurrentAsset . GetAsset ( ) - > PostEditChange ( ) ;
}
//Set that thumbnail as a valid custom thumbnail so it'll be saved out
NewThumbnail - > SetCreatedAfterCustomThumbsEnabled ( ) ;
}
}
}
}
}
}
void ContentBrowserUtils : : ClearCustomThumbnails ( const TArray < FAssetData > & InAssetsToAssign )
{
FAssetToolsModule & AssetToolsModule = FModuleManager : : LoadModuleChecked < FAssetToolsModule > ( " AssetTools " ) ;
//check if each asset should receive the new thumb nail
for ( auto AssetIt = InAssetsToAssign . CreateConstIterator ( ) ; AssetIt ; + + AssetIt )
{
const FAssetData & CurrentAsset = * AssetIt ;
// check whether this is a type that uses one of the shared static thumbnails
if ( AssetToolsModule . Get ( ) . AssetUsesGenericThumbnail ( CurrentAsset ) )
{
//assign the thumbnail and dirty
const FString ObjectFullName = CurrentAsset . GetFullName ( ) ;
const FString PackageName = CurrentAsset . PackageName . ToString ( ) ;
UPackage * AssetPackage = FindObject < UPackage > ( NULL , * PackageName ) ;
if ( ensure ( AssetPackage ) )
{
ThumbnailTools : : CacheEmptyThumbnail ( ObjectFullName , AssetPackage ) ;
//we need to indicate that the package needs to be resaved
AssetPackage - > MarkPackageDirty ( ) ;
// Signal that the asset was changed if it is loaded so thumbnail pools will update
if ( CurrentAsset . IsAssetLoaded ( ) )
{
CurrentAsset . GetAsset ( ) - > PostEditChange ( ) ;
}
}
}
}
}
bool ContentBrowserUtils : : AssetHasCustomThumbnail ( const FAssetData & AssetData )
{
FAssetToolsModule & AssetToolsModule = FModuleManager : : LoadModuleChecked < FAssetToolsModule > ( " AssetTools " ) ;
if ( AssetToolsModule . Get ( ) . AssetUsesGenericThumbnail ( AssetData ) )
{
const FObjectThumbnail * CachedThumbnail = ThumbnailTools : : FindCachedThumbnail ( AssetData . GetFullName ( ) ) ;
if ( CachedThumbnail ! = NULL & & ! CachedThumbnail - > IsEmpty ( ) )
{
return true ;
}
// If we don't yet have a thumbnail map, check the disk
FName ObjectFullName = FName ( * AssetData . GetFullName ( ) ) ;
TArray < FName > ObjectFullNames ;
FThumbnailMap LoadedThumbnails ;
ObjectFullNames . Add ( ObjectFullName ) ;
if ( ThumbnailTools : : ConditionallyLoadThumbnailsForObjects ( ObjectFullNames , LoadedThumbnails ) )
{
const FObjectThumbnail * Thumbnail = LoadedThumbnails . Find ( ObjectFullName ) ;
if ( Thumbnail ! = NULL & & ! Thumbnail - > IsEmpty ( ) )
{
return true ;
}
}
}
return false ;
}
bool ContentBrowserUtils : : IsEngineFolder ( const FString & InPath )
{
return InPath . StartsWith ( TEXT ( " /Engine " ) ) | | InPath = = TEXT ( " Engine " ) ;
}
bool ContentBrowserUtils : : IsDevelopersFolder ( const FString & InPath )
{
const FString DeveloperPathWithSlash = FPackageName : : FilenameToLongPackageName ( FPaths : : GameDevelopersDir ( ) ) ;
const FString DeveloperPathWithoutSlash = DeveloperPathWithSlash . LeftChop ( 1 ) ;
return InPath . StartsWith ( DeveloperPathWithSlash ) | | InPath = = DeveloperPathWithoutSlash ;
}
2014-07-02 13:18:43 -04:00
bool ContentBrowserUtils : : IsPluginFolder ( const FString & InPath )
{
FString PathWithSlash = InPath / TEXT ( " " ) ;
for ( const FPluginContentFolder & ContentFolder : IPluginManager : : Get ( ) . GetPluginContentFolders ( ) )
{
if ( PathWithSlash . StartsWith ( ContentFolder . RootPath ) | | InPath = = ContentFolder . Name )
{
return true ;
}
}
return false ;
}
2014-03-14 14:13:41 -04:00
void ContentBrowserUtils : : GetObjectsInAssetData ( const TArray < FAssetData > & AssetList , TArray < UObject * > & OutDroppedObjects )
{
for ( int32 AssetIdx = 0 ; AssetIdx < AssetList . Num ( ) ; + + AssetIdx )
{
const FAssetData & AssetData = AssetList [ AssetIdx ] ;
UObject * Obj = AssetData . GetAsset ( ) ;
if ( Obj )
{
OutDroppedObjects . Add ( Obj ) ;
}
}
}
bool ContentBrowserUtils : : IsValidFolderName ( const FString & FolderName , FText & Reason )
{
// Check length of the folder name
if ( FolderName . Len ( ) = = 0 )
{
Reason = LOCTEXT ( " InvalidFolderName_IsTooShort " , " Please provide a name for this folder. " ) ;
return false ;
}
if ( FolderName . Len ( ) > MAX_UNREAL_FILENAME_LENGTH )
{
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. " ) ,
FText : : FromString ( FolderName ) , FText : : AsNumber ( MAX_UNREAL_FILENAME_LENGTH ) ) ;
return false ;
}
const FString InvalidChars = INVALID_LONGPACKAGE_CHARACTERS TEXT ( " / " ) ; // Slash is an invalid character for a folder name
// See if the name contains invalid characters.
FString Char ;
for ( int32 CharIdx = 0 ; CharIdx < FolderName . Len ( ) ; + + CharIdx )
{
Char = FolderName . Mid ( CharIdx , 1 ) ;
if ( InvalidChars . Contains ( * Char ) )
{
FString ReadableInvalidChars = InvalidChars ;
ReadableInvalidChars . ReplaceInline ( TEXT ( " \r " ) , TEXT ( " " ) ) ;
ReadableInvalidChars . ReplaceInline ( TEXT ( " \n " ) , TEXT ( " " ) ) ;
ReadableInvalidChars . ReplaceInline ( TEXT ( " \t " ) , TEXT ( " " ) ) ;
Reason = FText : : Format ( LOCTEXT ( " InvalidFolderName_InvalidCharacters " , " A folder name may not contain any of the following characters: {0} " ) , FText : : FromString ( ReadableInvalidChars ) ) ;
return false ;
}
}
return FEditorFileUtils : : IsFilenameValidForSaving ( FolderName , Reason ) ;
}
bool ContentBrowserUtils : : DoesFolderExist ( const FString & FolderPath )
{
TArray < FString > SubPaths ;
FAssetRegistryModule & AssetRegistryModule = FModuleManager : : LoadModuleChecked < FAssetRegistryModule > ( TEXT ( " AssetRegistry " ) ) ;
AssetRegistryModule . Get ( ) . GetSubPaths ( FPaths : : GetPath ( FolderPath ) , SubPaths , false ) ;
for ( auto SubPathIt ( SubPaths . CreateConstIterator ( ) ) ; SubPathIt ; SubPathIt + + )
{
if ( * SubPathIt = = FolderPath )
{
return true ;
}
}
return false ;
}
bool ContentBrowserUtils : : IsAssetRootDir ( const FString & FolderPath )
{
return FolderPath = = TEXT ( " /Game " ) | | FolderPath = = TEXT ( " /Engine " ) | | FolderPath = = TEXT ( " /Classes " ) ;
}
const TSharedPtr < FLinearColor > ContentBrowserUtils : : LoadColor ( const FString & FolderPath )
{
// Ignore classes folder and newly created folders
if ( FolderPath ! = TEXT ( " /Classes " ) )
{
// Load the color from the config at this path
const FString RelativePath = FPackageName : : LongPackageNameToFilename ( FolderPath + TEXT ( " / " ) ) ;
// See if we have a value cached first
TSharedPtr < FLinearColor > * CachedColor = PathColors . Find ( RelativePath ) ;
if ( CachedColor )
{
return * CachedColor ;
}
// Loads the color of folder at the given path from the config
if ( FPaths : : FileExists ( GEditorUserSettingsIni ) )
{
// Create a new entry from the config, skip if it's default
FString ColorStr ;
if ( GConfig - > GetString ( TEXT ( " PathColor " ) , * RelativePath , ColorStr , GEditorUserSettingsIni ) )
{
FLinearColor Color ;
if ( Color . InitFromString ( ColorStr ) & & ! Color . Equals ( ContentBrowserUtils : : GetDefaultColor ( ) ) )
{
return PathColors . Add ( RelativePath , MakeShareable ( new FLinearColor ( Color ) ) ) ;
}
}
else
{
return PathColors . Add ( RelativePath , MakeShareable ( new FLinearColor ( ContentBrowserUtils : : GetDefaultColor ( ) ) ) ) ;
}
}
}
return NULL ;
}
void ContentBrowserUtils : : SaveColor ( const FString & FolderPath , const TSharedPtr < FLinearColor > FolderColor , bool bForceAdd )
{
check ( FolderPath ! = TEXT ( " /Classes " ) ) ;
const FString RelativePath = FPackageName : : LongPackageNameToFilename ( FolderPath + TEXT ( " / " ) ) ;
// Remove the color if it's invalid or default
const bool bRemove = ! FolderColor . IsValid ( ) | | ( ! bForceAdd & & FolderColor - > Equals ( ContentBrowserUtils : : GetDefaultColor ( ) ) ) ;
// Saves the color of the folder to the config
if ( FPaths : : FileExists ( GEditorUserSettingsIni ) )
{
// If this is no longer custom, remove it
if ( bRemove )
{
GConfig - > RemoveKey ( TEXT ( " PathColor " ) , * RelativePath , GEditorUserSettingsIni ) ;
}
else
{
GConfig - > SetString ( TEXT ( " PathColor " ) , * RelativePath , * FolderColor - > ToString ( ) , GEditorUserSettingsIni ) ;
}
}
// Update the map too
if ( bRemove )
{
PathColors . Remove ( RelativePath ) ;
}
else
{
PathColors . Add ( RelativePath , FolderColor ) ;
}
}
bool ContentBrowserUtils : : HasCustomColors ( TArray < FLinearColor > * OutColors )
{
// Check to see how many paths are currently using this color
// Note: we have to use the config, as paths which haven't been rendered yet aren't registered in the map
bool bHasCustom = false ;
if ( FPaths : : FileExists ( GEditorUserSettingsIni ) )
{
// Read individual entries from a config file.
TArray < FString > Section ;
GConfig - > GetSection ( TEXT ( " PathColor " ) , Section , GEditorUserSettingsIni ) ;
for ( int32 SectionIndex = 0 ; SectionIndex < Section . Num ( ) ; SectionIndex + + )
{
FString EntryStr = Section [ SectionIndex ] ;
EntryStr . Trim ( ) ;
FString PathStr ;
FString ColorStr ;
if ( EntryStr . Split ( TEXT ( " /= " ) , & PathStr , & ColorStr ) ) // DoesFolderExist doesn't like ending in a '/' so trim it here
{
// Ignore any that reference old folders
if ( FPackageName : : TryConvertFilenameToLongPackageName ( PathStr , PathStr ) & & DoesFolderExist ( PathStr ) )
{
// Ignore any that have invalid or default colors
FLinearColor CurrentColor ;
if ( CurrentColor . InitFromString ( ColorStr ) & & ! CurrentColor . Equals ( ContentBrowserUtils : : GetDefaultColor ( ) ) )
{
bHasCustom = true ;
if ( OutColors )
{
// Only add if not already present (ignores near matches too)
bool bAdded = false ;
for ( int32 ColorIndex = 0 ; ColorIndex < OutColors - > Num ( ) ; ColorIndex + + )
{
const FLinearColor & Color = ( * OutColors ) [ ColorIndex ] ;
if ( CurrentColor . Equals ( Color ) )
{
bAdded = true ;
break ;
}
}
if ( ! bAdded )
{
OutColors - > Add ( CurrentColor ) ;
}
}
else
{
break ;
}
}
}
}
}
}
return bHasCustom ;
}
FLinearColor ContentBrowserUtils : : GetDefaultColor ( )
{
// The default tint the folder should appear as
return FLinearColor : : Gray ;
}
FText ContentBrowserUtils : : GetExploreFolderText ( )
{
2014-04-23 17:41:37 -04:00
FFormatNamedArguments Args ;
Args . Add ( TEXT ( " FileManagerName " ) , FPlatformMisc : : GetFileManagerName ( ) ) ;
return FText : : Format ( NSLOCTEXT ( " GenericPlatform " , " ShowInFileManager " , " Show In {FileManagerName} " ) , Args ) ;
2014-03-14 14:13:41 -04:00
}
2014-09-01 13:25:23 -04:00
bool ContentBrowserUtils : : IsValidObjectPathForCreate ( const FString & ObjectPath , FText & OutErrorMessage , bool bAllowExistingAsset )
2014-08-15 16:14:32 -04:00
{
const FString ObjectName = FPackageName : : ObjectPathToObjectName ( ObjectPath ) ;
// Make sure the name is not already a class or otherwise invalid for saving
if ( ! FEditorFileUtils : : IsFilenameValidForSaving ( ObjectName , OutErrorMessage ) )
{
// Return false to indicate that the user should enter a new name
return false ;
}
// Make sure the new name only contains valid characters
if ( ! FName ( * ObjectName ) . IsValidXName ( INVALID_OBJECTNAME_CHARACTERS INVALID_LONGPACKAGE_CHARACTERS , & OutErrorMessage ) )
{
// Return false to indicate that the user should enter a new name
return false ;
}
// Make sure we are not creating an FName that is too large
if ( ObjectPath . Len ( ) > NAME_SIZE )
{
// This asset already exists at this location, inform the user and continue
OutErrorMessage = LOCTEXT ( " AssetNameTooLong " , " This asset name is too long. Please choose a shorter name. " ) ;
// Return false to indicate that the user should enter a new name
return false ;
}
const FString PackageName = FPackageName : : ObjectPathToPackageName ( ObjectPath ) ;
2014-08-26 11:11:44 -04:00
if ( ! IsValidPackageForCooking ( PackageName , OutErrorMessage ) )
2014-08-15 16:14:32 -04:00
{
return false ;
}
// Make sure we are not creating an path that is too long for the OS
const FString RelativePathFilename = FPackageName : : LongPackageNameToFilename ( PackageName , FPackageName : : GetAssetPackageExtension ( ) ) ; // full relative path with name + extension
const FString FullPath = FPaths : : ConvertRelativePathToFull ( RelativePathFilename ) ; // path to file on disk
if ( ObjectPath . Len ( ) > ( PLATFORM_MAX_FILEPATH_LENGTH - MAX_CLASS_NAME_LENGTH ) | | FullPath . Len ( ) > PLATFORM_MAX_FILEPATH_LENGTH )
{
// The full path for the asset is too long
OutErrorMessage = FText : : Format ( LOCTEXT ( " AssetPathTooLong " ,
" The full path for the asset is too deep, the maximum is '{0}'. \n Please choose a shorter name for the asset or create it in a shallower folder structure. " ) ,
FText : : AsNumber ( PLATFORM_MAX_FILEPATH_LENGTH ) ) ;
// Return false to indicate that the user should enter a new name
return false ;
}
2014-09-01 13:25:23 -04:00
// Check for an existing asset, unless it we were asked not to.
if ( ! bAllowExistingAsset )
2014-08-15 16:14:32 -04:00
{
2014-09-01 13:25:23 -04:00
FAssetRegistryModule & AssetRegistryModule = FModuleManager : : LoadModuleChecked < FAssetRegistryModule > ( " AssetRegistry " ) ;
FAssetData ExistingAsset = AssetRegistryModule . Get ( ) . GetAssetByObjectPath ( FName ( * ObjectPath ) ) ;
if ( ExistingAsset . IsValid ( ) )
{
// This asset already exists at this location, inform the user and continue
OutErrorMessage = FText : : Format ( LOCTEXT ( " RenameAssetAlreadyExists " , " An asset already exists at this location with the name '{0}'. " ) , FText : : FromString ( ObjectName ) ) ;
2014-08-15 16:14:32 -04:00
2014-09-01 13:25:23 -04:00
// Return false to indicate that the user should enter a new name
return false ;
}
2014-08-15 16:14:32 -04:00
}
return true ;
}
2014-08-18 12:24:23 -04:00
bool ContentBrowserUtils : : IsValidFolderPathForCreate ( const FString & InFolderPath , const FString & NewFolderName , FText & OutErrorMessage )
2014-08-15 16:14:32 -04:00
{
2014-08-18 12:24:23 -04:00
if ( ! ContentBrowserUtils : : IsValidFolderName ( NewFolderName , OutErrorMessage ) )
2014-08-15 16:14:32 -04:00
{
return false ;
}
2014-08-18 12:24:23 -04:00
const FString NewFolderPath = InFolderPath / NewFolderName ;
if ( ContentBrowserUtils : : DoesFolderExist ( NewFolderPath ) )
2014-08-15 16:14:32 -04:00
{
OutErrorMessage = LOCTEXT ( " RenameFolderAlreadyExists " , " A folder already exists at this location with this name. " ) ;
return false ;
}
// Make sure we are not creating a folder path that is too long
2014-08-18 12:24:23 -04:00
if ( NewFolderPath . Len ( ) > PLATFORM_MAX_FILEPATH_LENGTH - MAX_CLASS_NAME_LENGTH )
2014-08-15 16:14:32 -04:00
{
// The full path for the folder is too long
OutErrorMessage = FText : : Format ( LOCTEXT ( " RenameFolderPathTooLong " ,
" 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. " ) ,
FText : : AsNumber ( PLATFORM_MAX_FILEPATH_LENGTH ) ) ;
// Return false to indicate that the user should enter a new name for the folder
return false ;
}
return true ;
}
2014-08-26 11:11:44 -04:00
bool ContentBrowserUtils : : IsValidPackageForCooking ( const FString & PackageName , FText & OutErrorMessage )
{
// We assume the game name is 20 characters (the maximum allowed) to make sure that content can be ported between projects
// 260 characters is the limit on Windows, which is the shortest max path of any platforms that support cooking
static const int32 MaxGameNameLen = 20 ;
static const int32 MaxPathLen = 260 ;
// Pad out the game name to the maximum allowed
const FString GameName = FApp : : GetGameName ( ) ;
FString GameNamePadded = GameName ;
while ( GameNamePadded . Len ( ) < MaxGameNameLen )
{
GameNamePadded + = TEXT ( " " ) ;
}
// We use "WindowsNoEditor" below as it's the longest platform name, so will also prove that any shorter platform names will validate correctly
const FString AbsoluteRootPath = FPaths : : ConvertRelativePathToFull ( FPaths : : RootDir ( ) ) ;
const FString AbsoluteGamePath = FPaths : : ConvertRelativePathToFull ( FPaths : : GameDir ( ) ) ;
const FString AbsoluteCookPath = AbsoluteGamePath / TEXT ( " Saved " ) / TEXT ( " Cooked " ) / TEXT ( " WindowsNoEditor " ) / GameName ;
const FString RelativePathToAsset = FPackageName : : LongPackageNameToFilename ( PackageName , FPackageName : : GetAssetPackageExtension ( ) ) ;
const FString AbsolutePathToAsset = FPaths : : ConvertRelativePathToFull ( RelativePathToAsset ) ;
FString AssetPathWithinCookDir = AbsolutePathToAsset ;
FPaths : : RemoveDuplicateSlashes ( AssetPathWithinCookDir ) ;
AssetPathWithinCookDir . RemoveFromStart ( AbsoluteGamePath , ESearchCase : : CaseSensitive ) ;
// Test that the package can be cooked based on the current project path
{
FString AbsoluteCookPathToAsset = AbsoluteCookPath / AssetPathWithinCookDir ;
AbsoluteCookPathToAsset . ReplaceInline ( * GameName , * GameNamePadded , ESearchCase : : CaseSensitive ) ;
if ( AbsoluteCookPathToAsset . Len ( ) > MaxPathLen )
{
// The projected length of the path for cooking is too long
OutErrorMessage = LOCTEXT ( " AssetCookingPathTooLong " , " The path to the asset is too long for cooking \n Please choose a shorter name for the asset or create it in a shallower folder structure with shorter folder names. " ) ;
// Return false to indicate that the user should enter a new name
return false ;
}
}
// See TTP# 332328:
// The following checks are done mostly to prevent / alleviate the problems that "long" paths are causing with the BuildFarm and cooked builds.
// 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.
// - We assume a base path of D:/BuildFarm/buildmachine_++depot+UE4-Releases+4.10/
// - We assume the game name is 20 characters (the maximum allowed) to make sure that content can be ported between projects
// - We calculate the cooked game path relative to the game root (eg, Showcases/Infiltrator/Saved/Cooked/WindowsNoEditor/Infiltrator)
// - 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)
if ( FEngineBuildSettings : : IsInternalBuild ( ) )
{
// We assume a constant size for the build machine base path, so strip either the root or game path from the start
// (depending on whether the project is part of the main UE4 source tree or located elsewhere)
FString CookDirWithoutBasePath = AbsoluteCookPath ;
if ( CookDirWithoutBasePath . StartsWith ( AbsoluteRootPath , ESearchCase : : CaseSensitive ) )
{
CookDirWithoutBasePath . RemoveFromStart ( AbsoluteRootPath , ESearchCase : : CaseSensitive ) ;
}
else
{
CookDirWithoutBasePath . RemoveFromStart ( AbsoluteGamePath , ESearchCase : : CaseSensitive ) ;
}
FString AbsoluteBuildMachineCookPathToAsset = FString ( TEXT ( " D:/BuildFarm/buildmachine_++depot+UE4-Releases+4.10 " ) ) / CookDirWithoutBasePath / AssetPathWithinCookDir ;
AbsoluteBuildMachineCookPathToAsset . ReplaceInline ( * GameName , * GameNamePadded , ESearchCase : : CaseSensitive ) ;
if ( AbsoluteBuildMachineCookPathToAsset . Len ( ) > MaxPathLen )
{
// The projected length of the path for cooking is too long
OutErrorMessage = LOCTEXT ( " AssetCookingPathTooLongForBuildMachine " , " The path to the asset is too long for cooking by the build machines \n Please choose a shorter name for the asset or create it in a shallower folder structure with shorter folder names. " ) ;
// Return false to indicate that the user should enter a new name
return false ;
}
}
return true ;
}
2014-03-14 14:13:41 -04:00
# undef LOCTEXT_NAMESPACE