329 lines
9.6 KiB
C++
329 lines
9.6 KiB
C++
// Copyright Epic Games, Inc. All Rights Reserved.
|
|
|
|
#include "CoreMinimal.h"
|
|
#include "Modules/ModuleManager.h"
|
|
#include "UObject/UObjectHash.h"
|
|
#include "UObject/UObjectIterator.h"
|
|
#include "Widgets/DeclarativeSyntaxSupport.h"
|
|
#include "ISettingsContainer.h"
|
|
#include "ISettingsEditorModel.h"
|
|
#include "Models/SettingsEditorModel.h"
|
|
#include "Widgets/SWidget.h"
|
|
#include "Framework/Notifications/NotificationManager.h"
|
|
#include "Widgets/Notifications/SNotificationList.h"
|
|
#include "Widgets/SSettingsEditor.h"
|
|
|
|
#include "ISettingsEditorModule.h"
|
|
#include "ISettingsModule.h"
|
|
#include "Engine/DeveloperSettings.h"
|
|
#include "Engine/PlatformSettings.h"
|
|
#include "PropertyEditorModule.h"
|
|
#include "PropertyEditorDelegates.h"
|
|
#include "Customizations/PerPlatformSettingsCustomization.h"
|
|
|
|
#define LOCTEXT_NAMESPACE "SSettingsEditor"
|
|
|
|
/** Holds auto discovered settings information so that they can be unloaded automatically when refreshing. */
|
|
struct FRegisteredSettings
|
|
{
|
|
FName ContainerName;
|
|
FName CategoryName;
|
|
FName SectionName;
|
|
};
|
|
|
|
|
|
/** Manages the notification for when the application needs to be restarted due to a settings change */
|
|
class FApplicationRestartRequiredNotification
|
|
{
|
|
public:
|
|
void SetOnRestartApplicationCallback( FSimpleDelegate InRestartApplicationDelegate )
|
|
{
|
|
RestartApplicationDelegate = InRestartApplicationDelegate;
|
|
}
|
|
|
|
void OnRestartRequired()
|
|
{
|
|
TSharedPtr<SNotificationItem> NotificationPin = NotificationPtr.Pin();
|
|
if (NotificationPin.IsValid() || !RestartApplicationDelegate.IsBound())
|
|
{
|
|
return;
|
|
}
|
|
|
|
FNotificationInfo Info( LOCTEXT("RestartRequiredTitle", "Restart required to apply new settings") );
|
|
|
|
// Add the buttons with text, tooltip and callback
|
|
Info.ButtonDetails.Add(FNotificationButtonInfo(
|
|
LOCTEXT("RestartNow", "Restart Now"),
|
|
LOCTEXT("RestartNowToolTip", "Restart now to finish applying your new settings."),
|
|
FSimpleDelegate::CreateRaw(this, &FApplicationRestartRequiredNotification::OnRestartClicked))
|
|
);
|
|
Info.ButtonDetails.Add(FNotificationButtonInfo(
|
|
LOCTEXT("RestartLater", "Restart Later"),
|
|
LOCTEXT("RestartLaterToolTip", "Dismiss this notificaton without restarting. Some new settings will not be applied."),
|
|
FSimpleDelegate::CreateRaw(this, &FApplicationRestartRequiredNotification::OnDismissClicked))
|
|
);
|
|
|
|
// We will be keeping track of this ourselves
|
|
Info.bFireAndForget = false;
|
|
|
|
// Set the width so that the notification doesn't resize as its text changes
|
|
Info.WidthOverride = 300.0f;
|
|
|
|
Info.bUseLargeFont = false;
|
|
Info.bUseThrobber = false;
|
|
Info.bUseSuccessFailIcons = false;
|
|
|
|
// Launch notification
|
|
NotificationPtr = FSlateNotificationManager::Get().AddNotification(Info);
|
|
NotificationPin = NotificationPtr.Pin();
|
|
|
|
if (NotificationPin.IsValid())
|
|
{
|
|
NotificationPin->SetCompletionState(SNotificationItem::CS_Pending);
|
|
}
|
|
}
|
|
|
|
private:
|
|
void OnRestartClicked()
|
|
{
|
|
TSharedPtr<SNotificationItem> NotificationPin = NotificationPtr.Pin();
|
|
if (NotificationPin.IsValid())
|
|
{
|
|
NotificationPin->SetText(LOCTEXT("RestartingNow", "Restarting..."));
|
|
NotificationPin->SetCompletionState(SNotificationItem::CS_Success);
|
|
NotificationPin->ExpireAndFadeout();
|
|
NotificationPtr.Reset();
|
|
}
|
|
|
|
RestartApplicationDelegate.ExecuteIfBound();
|
|
}
|
|
|
|
void OnDismissClicked()
|
|
{
|
|
TSharedPtr<SNotificationItem> NotificationPin = NotificationPtr.Pin();
|
|
if (NotificationPin.IsValid())
|
|
{
|
|
NotificationPin->SetText(LOCTEXT("RestartDismissed", "Restart Dismissed..."));
|
|
NotificationPin->SetCompletionState(SNotificationItem::CS_None);
|
|
NotificationPin->ExpireAndFadeout();
|
|
NotificationPtr.Reset();
|
|
}
|
|
}
|
|
|
|
/** Used to reference to the active restart notification */
|
|
TWeakPtr<SNotificationItem> NotificationPtr;
|
|
|
|
/** Used to actually restart the application */
|
|
FSimpleDelegate RestartApplicationDelegate;
|
|
};
|
|
|
|
|
|
/**
|
|
* Implements the SettingsEditor module.
|
|
*/
|
|
class FSettingsEditorModule
|
|
: public ISettingsEditorModule
|
|
{
|
|
public:
|
|
|
|
FSettingsEditorModule()
|
|
: bAreSettingsStale(true)
|
|
{
|
|
}
|
|
|
|
// IModuleInterface interface
|
|
|
|
virtual void StartupModule() override
|
|
{
|
|
FPropertyEditorModule& PropertyModule = FModuleManager::LoadModuleChecked<FPropertyEditorModule>("PropertyEditor");
|
|
PropertyModule.RegisterCustomPropertyTypeLayout(
|
|
StaticStruct<FPerPlatformSettings>()->GetFName(),
|
|
FOnGetPropertyTypeCustomizationInstance::CreateStatic(&FPerPlatformSettingsCustomization::MakeInstance)
|
|
);
|
|
|
|
FModuleManager::Get().OnModulesChanged().AddRaw(this, &FSettingsEditorModule::ModulesChangesCallback);
|
|
}
|
|
|
|
virtual void ShutdownModule() override
|
|
{
|
|
ISettingsModule* SettingsModule = FModuleManager::GetModulePtr<ISettingsModule>("Settings");
|
|
|
|
if ( SettingsModule != nullptr )
|
|
{
|
|
UnregisterAutoDiscoveredSettings(*SettingsModule);
|
|
}
|
|
|
|
FModuleManager::Get().OnModulesChanged().RemoveAll(this);
|
|
}
|
|
|
|
// ISettingsEditorModule interface
|
|
|
|
virtual TSharedRef<SWidget> CreateEditor( const TSharedRef<ISettingsEditorModel>& Model ) override
|
|
{
|
|
UpdateSettings(true);
|
|
|
|
TSharedRef<SWidget> Editor = SNew(SSettingsEditor, Model)
|
|
.OnApplicationRestartRequired(FSimpleDelegate::CreateRaw(this, &FSettingsEditorModule::OnApplicationRestartRequired));
|
|
|
|
ClearStaleEditorWidgets();
|
|
EditorWidgets.Add(Editor);
|
|
|
|
return Editor;
|
|
}
|
|
|
|
virtual ISettingsEditorModelRef CreateModel( const TSharedRef<ISettingsContainer>& SettingsContainer ) override
|
|
{
|
|
return MakeShareable(new FSettingsEditorModel(SettingsContainer));
|
|
}
|
|
|
|
virtual void OnApplicationRestartRequired() override
|
|
{
|
|
ApplicationRestartRequiredNotification.OnRestartRequired();
|
|
}
|
|
|
|
virtual void SetRestartApplicationCallback( FSimpleDelegate InRestartApplicationDelegate ) override
|
|
{
|
|
ApplicationRestartRequiredNotification.SetOnRestartApplicationCallback(InRestartApplicationDelegate);
|
|
}
|
|
|
|
virtual void SetShouldRegisterSettingCallback(FShouldRegisterSettingsDelegate InShouldRegisterSettingDelegate) override
|
|
{
|
|
ShouldRegisterSettingsDelegate = MoveTemp(InShouldRegisterSettingDelegate);
|
|
}
|
|
|
|
private:
|
|
|
|
void ModulesChangesCallback(FName ModuleName, EModuleChangeReason ReasonForChange)
|
|
{
|
|
ClearStaleEditorWidgets();
|
|
bAreSettingsStale = true;
|
|
UpdateSettings();
|
|
}
|
|
|
|
virtual void UpdateSettings(bool bForce = false) override
|
|
{
|
|
if ( ( AnyActiveSettingsEditor() || bForce ) && bAreSettingsStale )
|
|
{
|
|
bAreSettingsStale = false;
|
|
|
|
ISettingsModule* SettingsModule = FModuleManager::GetModulePtr<ISettingsModule>("Settings");
|
|
|
|
if ( SettingsModule != nullptr )
|
|
{
|
|
UnregisterAutoDiscoveredSettings(*SettingsModule);
|
|
RegisterAutoDiscoveredSettings(*SettingsModule);
|
|
}
|
|
}
|
|
}
|
|
|
|
void ClearStaleEditorWidgets()
|
|
{
|
|
for ( int32 i = 0; i < EditorWidgets.Num(); i++ )
|
|
{
|
|
if ( !EditorWidgets[i].IsValid() )
|
|
{
|
|
EditorWidgets.RemoveAtSwap(i);
|
|
i--;
|
|
}
|
|
}
|
|
}
|
|
|
|
bool AnyActiveSettingsEditor()
|
|
{
|
|
return EditorWidgets.Num() > 0;
|
|
}
|
|
|
|
private:
|
|
|
|
void RegisterAutoDiscoveredSettings(ISettingsModule& SettingsModule)
|
|
{
|
|
// Find game object
|
|
for ( TObjectIterator<UDeveloperSettings> SettingsIt(RF_NoFlags); SettingsIt; ++SettingsIt )
|
|
{
|
|
if ( UDeveloperSettings* Settings = *SettingsIt )
|
|
{
|
|
// Only Add the CDO of any UDeveloperSettings objects.
|
|
if ( Settings->HasAnyFlags(RF_ClassDefaultObject) && !Settings->GetClass()->HasAnyClassFlags(CLASS_Deprecated | CLASS_Abstract) )
|
|
{
|
|
// Ignore the setting if it's specifically the UDeveloperSettings or other abstract settings classes
|
|
if ( Settings->GetClass()->HasAnyClassFlags(CLASS_Abstract) || !Settings->SupportsAutoRegistration() )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
// Let an external module decide if the setting should be registered.
|
|
if (ShouldRegisterSettingsDelegate.IsBound() && !ShouldRegisterSettingsDelegate.Execute(Settings))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
RegisterDeveloperSettings(SettingsModule, Settings);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void RegisterDeveloperSettings(ISettingsModule& SettingsModule, UDeveloperSettings* Settings)
|
|
{
|
|
FRegisteredSettings Registered;
|
|
Registered.ContainerName = Settings->GetContainerName();
|
|
Registered.CategoryName = Settings->GetCategoryName();
|
|
Registered.SectionName = Settings->GetSectionName();
|
|
|
|
TSharedPtr<SWidget> CustomWidget = Settings->GetCustomSettingsWidget();
|
|
if (CustomWidget.IsValid())
|
|
{
|
|
// Add Settings
|
|
SettingsModule.RegisterSettings(Registered.ContainerName, Registered.CategoryName, Registered.SectionName,
|
|
Settings->GetSectionText(),
|
|
Settings->GetSectionDescription(),
|
|
CustomWidget.ToSharedRef()
|
|
);
|
|
}
|
|
else
|
|
{
|
|
// Add Settings
|
|
SettingsModule.RegisterSettings(Registered.ContainerName, Registered.CategoryName, Registered.SectionName,
|
|
Settings->GetSectionText(),
|
|
Settings->GetSectionDescription(),
|
|
Settings
|
|
);
|
|
}
|
|
|
|
AutoDiscoveredSettings.Add(Registered);
|
|
}
|
|
|
|
void UnregisterAutoDiscoveredSettings(ISettingsModule& SettingsModule)
|
|
{
|
|
// Unregister any auto discovers settings.
|
|
for ( const FRegisteredSettings& Settings : AutoDiscoveredSettings )
|
|
{
|
|
SettingsModule.UnregisterSettings(Settings.ContainerName, Settings.CategoryName, Settings.SectionName);
|
|
}
|
|
|
|
AutoDiscoveredSettings.Reset();
|
|
}
|
|
|
|
private:
|
|
|
|
FApplicationRestartRequiredNotification ApplicationRestartRequiredNotification;
|
|
|
|
/** The list of auto discovered settings that need to be unregistered. */
|
|
TArray<FRegisteredSettings> AutoDiscoveredSettings;
|
|
|
|
/** Living editor widgets that have been handed out. */
|
|
TArray< TWeakPtr<SWidget> > EditorWidgets;
|
|
|
|
/** Flag if the settings are stale currently and need to be refreshed. */
|
|
bool bAreSettingsStale;
|
|
|
|
/** Delegate called to decide if a settings object should be registered with the module. */
|
|
FShouldRegisterSettingsDelegate ShouldRegisterSettingsDelegate;
|
|
};
|
|
|
|
|
|
IMPLEMENT_MODULE(FSettingsEditorModule, SettingsEditor);
|
|
|
|
|
|
#undef LOCTEXT_NAMESPACE
|