Files
UnrealEngine/Engine/Source/Editor/DetailCustomizations/Private/MoviePlayerSettingsDetails.cpp
2025-05-18 13:04:45 +08:00

212 lines
7.3 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "MoviePlayerSettingsDetails.h"
#include "Containers/Array.h"
#include "Delegates/Delegate.h"
#include "DetailCategoryBuilder.h"
#include "DetailLayoutBuilder.h"
#include "DetailWidgetRow.h"
#include "Dialogs/Dialogs.h"
#include "EditorDirectories.h"
#include "Framework/Notifications/NotificationManager.h"
#include "HAL/FileManager.h"
#include "HAL/PlatformCrt.h"
#include "IDetailChildrenBuilder.h"
#include "IDetailPropertyRow.h"
#include "Internationalization/Internationalization.h"
#include "Internationalization/Text.h"
#include "Misc/Attribute.h"
#include "Misc/Paths.h"
#include "PropertyCustomizationHelpers.h"
#include "PropertyEditorModule.h"
#include "PropertyHandle.h"
#include "SourceControlHelpers.h"
#include "Styling/AppStyle.h"
#include "Widgets/DeclarativeSyntaxSupport.h"
#include "Widgets/Input/SFilePathPicker.h"
#include "Widgets/Notifications/SNotificationList.h"
#define LOCTEXT_NAMESPACE "MoviePlayerSettingsDetails"
FMoviePlayerSettingsDetails::~FMoviePlayerSettingsDetails()
{
}
TSharedRef<IDetailCustomization> FMoviePlayerSettingsDetails::MakeInstance()
{
return MakeShareable(new FMoviePlayerSettingsDetails);
}
void FMoviePlayerSettingsDetails::CustomizeDetails( IDetailLayoutBuilder& DetailLayout )
{
IDetailCategoryBuilder& MoviesCategory = DetailLayout.EditCategory("Movies");
StartupMoviesPropertyHandle = DetailLayout.GetProperty("StartupMovies");
TSharedRef<FDetailArrayBuilder> StartupMoviesBuilder = MakeShareable( new FDetailArrayBuilder( StartupMoviesPropertyHandle.ToSharedRef() ) );
StartupMoviesBuilder->OnGenerateArrayElementWidget( FOnGenerateArrayElementWidget::CreateSP(this, &FMoviePlayerSettingsDetails::GenerateArrayElementWidget) );
MoviesCategory.AddProperty( "bWaitForMoviesToComplete" );
MoviesCategory.AddProperty( "bMoviesAreSkippable" );
const bool bForAdvanced = false;
MoviesCategory.AddCustomBuilder( StartupMoviesBuilder, bForAdvanced );
}
void FMoviePlayerSettingsDetails::GenerateArrayElementWidget(TSharedRef<IPropertyHandle> PropertyHandle, int32 ArrayIndex, IDetailChildrenBuilder& ChildrenBuilder)
{
IDetailPropertyRow& FilePathRow = ChildrenBuilder.AddProperty( PropertyHandle );
{
FilePathRow.CustomWidget(false)
.NameContent()
[
PropertyHandle->CreatePropertyNameWidget()
]
.ValueContent()
.MaxDesiredWidth(0.0f)
.MinDesiredWidth(125.0f)
[
SNew(SFilePathPicker)
.BrowseButtonImage(FAppStyle::GetBrush("PropertyWindow.Button_Ellipsis"))
.BrowseButtonStyle(FAppStyle::Get(), "HoverHintOnly")
.BrowseButtonToolTip(LOCTEXT("FileButtonToolTipText", "Choose a file from this computer"))
.BrowseDirectory(FEditorDirectories::Get().GetLastDirectory(ELastDirectory::GENERIC_OPEN))
.BrowseTitle(LOCTEXT("PropertyEditorTitle", "File picker..."))
.FilePath(this, &FMoviePlayerSettingsDetails::HandleFilePathPickerFilePath, PropertyHandle)
.FileTypeFilter(TEXT("MPEG-4 Movie (*.mp4)|*.mp4|Web Movie (*.webm)|*.webm"))
.OnPathPicked(this, &FMoviePlayerSettingsDetails::HandleFilePathPickerPathPicked, PropertyHandle)
];
}
};
void FMoviePlayerSettingsDetails::HandleFilePathPickerPathPicked( const FString& PickedPath, TSharedRef<IPropertyHandle> Property )
{
// Ignore if value is unchanged.
FString OldValue;
if (Property->GetValueAsFormattedString(OldValue) == FPropertyAccess::Success)
{
if (PickedPath == OldValue)
{
return;
}
}
FEditorDirectories::Get().SetLastDirectory(ELastDirectory::GENERIC_OPEN, FPaths::GetPath(PickedPath));
// sanitize the location of the chosen movies to the content/movies directory
const FString MoviesBaseDir = FPaths::ConvertRelativePathToFull(FPaths::ProjectContentDir() + TEXT("Movies/"));
const FString FullPath = FPaths::ConvertRelativePathToFull(PickedPath);
if (FullPath.StartsWith(MoviesBaseDir))
{
// mark for add/checkout
FText FailReason;
if (SourceControlHelpers::CheckoutOrMarkForAdd(PickedPath, LOCTEXT("MovieFileDescription", "movie"), FOnPostCheckOut(), FailReason))
{
// already in the movies dir, so just trim the path so we just have a partial path with no extension (the movie player expects this)
Property->SetValue(FPaths::GetBaseFilename(FullPath.RightChop(MoviesBaseDir.Len())));
}
else
{
FNotificationInfo Info(FailReason);
Info.ExpireDuration = 3.0f;
FSlateNotificationManager::Get().AddNotification(Info);
}
}
else if (!PickedPath.IsEmpty())
{
// If the path of PickedPath is empty then we are dealing with a movie already in the correct directory.
if (FPaths::GetPath(PickedPath).IsEmpty())
{
FString FullPickedPath = FPaths::Combine(MoviesBaseDir, PickedPath);
// Look for this movie.
TArray<FString> ExistingMovieFiles;
IFileManager::Get().FindFiles(ExistingMovieFiles, *MoviesBaseDir);
bool bHasValidMovie = ExistingMovieFiles.ContainsByPredicate(
[&PickedPath](const FString& ExistingMovie)
{
return ExistingMovie.Contains(PickedPath);
});
// Did we find a movie?
if (bHasValidMovie)
{
// Yes.
Property->SetValue(FPaths::GetBaseFilename(PickedPath));
}
else
{
// Nope. Bring up a dialog box informing the user.
FSuppressableWarningDialog::FSetupInfo Info(
LOCTEXT("ExternalMovieImportNotExistWarning", "This movie does not exist."),
LOCTEXT("ExternalMovieImportNotExistTitle", "Does not exist"),
TEXT("ImportMovieIntoProjectNotExist"));
Info.ConfirmText = LOCTEXT("ExternalMovieImportNotExist_Confirm", "OK");
Info.CancelText = LOCTEXT("ExternalMovieImportNotExist_Cancel", "Cancel");
FSuppressableWarningDialog ImportWarningDialog(Info);
if (ImportWarningDialog.ShowModal() == FSuppressableWarningDialog::EResult::Cancel)
{
Property->SetValue(FPaths::GetBaseFilename(PickedPath));
}
}
}
else
{
// ask the user if they want to import this movie
FSuppressableWarningDialog::FSetupInfo Info(
LOCTEXT("ExternalMovieImportWarning", "This movie needs to be copied into your project, would you like to copy the file now?"),
LOCTEXT("ExternalMovieImportTitle", "Copy Movie"),
TEXT("ImportMovieIntoProject"));
Info.ConfirmText = LOCTEXT("ExternalMovieImport_Confirm", "Copy");
Info.CancelText = LOCTEXT("ExternalMovieImport_Cancel", "Don't Copy");
FSuppressableWarningDialog ImportWarningDialog(Info);
if (ImportWarningDialog.ShowModal() != FSuppressableWarningDialog::EResult::Cancel)
{
const FString FileName = FPaths::GetCleanFilename(PickedPath);
const FString DestPath = MoviesBaseDir / FileName;
FText FailReason;
if (SourceControlHelpers::CopyFileUnderSourceControl(DestPath, PickedPath, LOCTEXT("MovieFileDescription", "movie"), FailReason))
{
// trim the path so we just have a partial path with no extension (the movie player expects this)
Property->SetValue(FPaths::GetBaseFilename(DestPath.RightChop(MoviesBaseDir.Len())));
}
else
{
FNotificationInfo FailureInfo(FailReason);
FailureInfo.ExpireDuration = 3.0f;
FSlateNotificationManager::Get().AddNotification(FailureInfo);
}
}
}
}
else
{
Property->SetValue(PickedPath);
}
}
FString FMoviePlayerSettingsDetails::HandleFilePathPickerFilePath( TSharedRef<IPropertyHandle> Property ) const
{
FString FilePath;
Property->GetValue(FilePath);
return FilePath;
}
#undef LOCTEXT_NAMESPACE