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

1700 lines
54 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "Toolkits/AssetEditorToolkit.h"
#include "Widgets/Layout/SBorder.h"
#include "Framework/MultiBox/MultiBoxDefs.h"
#include "Framework/MultiBox/MultiBoxBuilder.h"
#include "GameFramework/Actor.h"
#include "Editor.h"
#include "Misc/ConfigCacheIni.h"
#include "Modules/ModuleManager.h"
#include "Styling/AppStyle.h"
#include "Settings/EditorStyleSettings.h"
#include "EditorReimportHandler.h"
#include "FileHelpers.h"
#include "Toolkits/SStandaloneAssetEditorToolkitHost.h"
#include "Toolkits/ToolkitManager.h"
#include "Toolkits/AssetEditorCommonCommands.h"
#include "Toolkits/GlobalEditorCommonCommands.h"
#include "Styling/SlateIconFinder.h"
#include "CollectionManagerTypes.h"
#include "ICollectionContainer.h"
#include "ICollectionManager.h"
#include "ICollectionSource.h"
#include "CollectionManagerModule.h"
#include "Widgets/SToolTip.h"
#include "IDocumentation.h"
#include "Widgets/Docking/SDockTab.h"
#include "IAssetTools.h"
#include "IAssetTypeActions.h"
#include "AssetToolsModule.h"
#include "Toolkits/AssetEditorToolkitMenuContext.h"
#include "ToolMenus.h"
#include "Subsystems/AssetEditorSubsystem.h"
#include "Logging/LogMacros.h"
#include "AssetEditorModeManager.h"
#include "Misc/Attribute.h"
#include "Textures/SlateIcon.h"
#include "WidgetDrawerConfig.h"
#include "Framework/Commands/GenericCommands.h"
#include "Interfaces/Interface_AsyncCompilation.h"
#include "Widgets/Images/SImage.h"
#include "StatusBarSubsystem.h"
#define LOCTEXT_NAMESPACE "AssetEditorToolkit"
DEFINE_LOG_CATEGORY_STATIC(LogAssetEditorToolkit, Log, All);
TWeakPtr< IToolkitHost > FAssetEditorToolkit::PreviousWorldCentricToolkitHostForNewAssetEditor;
TSharedPtr<FExtensibilityManager> FAssetEditorToolkit::SharedMenuExtensibilityManager;
TSharedPtr<FExtensibilityManager> FAssetEditorToolkit::SharedToolBarExtensibilityManager;
const FName FAssetEditorToolkit::DefaultAssetEditorToolBarName("AssetEditor.DefaultToolBar");
const FName FAssetEditorToolkit::ReadOnlyMenuProfileName("AssetEditor.ReadOnlyMenuProfile");
FAssetEditorToolkit::FAssetEditorToolkit()
: GCEditingObjects(*this)
, bCheckDirtyOnAssetSave(false)
, AssetEditorModeManager(nullptr)
, bIsToolbarFocusable(false)
, bIsToolbarUsingSmallIcons(false)
, OpenMethod(EAssetOpenMethod::Edit)
{
WorkspaceMenuCategory = FWorkspaceItem::NewGroup(LOCTEXT("WorkspaceMenu_BaseAssetEditor", "Asset Editor"));
if (!FAssetEditorCommonCommands::IsRegistered())
{
// Ensure the asset editor common commands are registered in case UnrealEdMisc isn't used, and therefore not initializing the commands.
FAssetEditorCommonCommands::Register();
}
}
void FAssetEditorToolkit::InitAssetEditor( const EToolkitMode::Type Mode, const TSharedPtr< class IToolkitHost >& InitToolkitHost, const FName AppIdentifier, const TSharedRef<FTabManager::FLayout>& StandaloneDefaultLayout, const bool bCreateDefaultStandaloneMenu, const bool bCreateDefaultToolbar, UObject* ObjectToEdit, const bool bInIsToolbarFocusable, const bool bInUseSmallToolbarIcons, const TOptional<EAssetOpenMethod>& InOpenMethod )
{
TArray< UObject* > ObjectsToEdit;
ObjectsToEdit.Add( ObjectToEdit );
InitAssetEditor( Mode, InitToolkitHost, AppIdentifier, StandaloneDefaultLayout, bCreateDefaultStandaloneMenu, bCreateDefaultToolbar, ObjectsToEdit, bInIsToolbarFocusable, bInUseSmallToolbarIcons );
}
void FAssetEditorToolkit::InitAssetEditor( const EToolkitMode::Type Mode, const TSharedPtr< class IToolkitHost >& InitToolkitHost, const FName AppIdentifier, const TSharedRef<FTabManager::FLayout>& StandaloneDefaultLayout, const bool bCreateDefaultStandaloneMenu, const bool bCreateDefaultToolbar, const TArray<UObject*>& ObjectsToEdit, const bool bInIsToolbarFocusable, const bool bInUseSmallToolbarIcons, const TOptional<EAssetOpenMethod>& InOpenMethod )
{
// Must not already be editing an object
check( ObjectsToEdit.Num() > 0 );
check( EditingObjects.Num() == 0 );
bIsToolbarFocusable = bInIsToolbarFocusable;
bIsToolbarUsingSmallIcons = bInUseSmallToolbarIcons;
// cache reference to ToolkitManager; also ensure it was initialized.
FToolkitManager& ToolkitManager = FToolkitManager::Get();
EditingObjects.Append( ObjectsToEdit );
// If the open method was manually overriden, use that
if(InOpenMethod.IsSet())
{
OpenMethod = InOpenMethod.GetValue();
}
// Otherwise we check the Asset Editor Subsystem to see if there is a method this asset editor is being requested to open in
else
{
TOptional<EAssetOpenMethod> CachedOpenMethod = GEditor->GetEditorSubsystem<UAssetEditorSubsystem>()->GetAssetsBeingOpenedMethod(EditingObjects);
if(CachedOpenMethod.IsSet())
{
OpenMethod = CachedOpenMethod.GetValue();
}
}
// Store "previous" asset editing toolkit host, and clear it out
PreviousWorldCentricToolkitHost = PreviousWorldCentricToolkitHostForNewAssetEditor;
PreviousWorldCentricToolkitHostForNewAssetEditor.Reset();
ToolkitMode = Mode;
TSharedPtr<SWindow> ParentWindow;
TSharedPtr<SDockTab> NewMajorTab;
TSharedPtr< SStandaloneAssetEditorToolkitHost > NewStandaloneHost;
if( ToolkitMode == EToolkitMode::WorldCentric ) // @todo toolkit major: Do we need to remember this setting on a per-asset editor basis? Probably.
{
// Keep track of the level editor we're attached to (if any)
ToolkitHost = InitToolkitHost;
}
else if( ensure( ToolkitMode == EToolkitMode::Standalone ) )
{
// Open a standalone app to edit this asset.
check( AppIdentifier != NAME_None );
// Create the label and the link for the toolkit documentation.
TAttribute<FText> Label = TAttribute<FText>( this, &FAssetEditorToolkit::GetToolkitName );
TAttribute<FText> LabelSuffix = TAttribute<FText>(this, &FAssetEditorToolkit::GetTabSuffix);
TAttribute<FText> ToolTipText = TAttribute<FText>( this, &FAssetEditorToolkit::GetToolkitToolTipText );
FString DocLink = GetDocumentationLink();
if ( !DocLink.StartsWith( "Shared/" ) )
{
DocLink = FString("Shared/") + DocLink;
}
// Create a new SlateToolkitHost
NewMajorTab = SNew(SDockTab)
.ContentPadding(0.0f)
.TabRole(ETabRole::MajorTab)
.ToolTip(IDocumentation::Get()->CreateToolTip(ToolTipText, nullptr, DocLink, GetToolkitFName().ToString()))
.IconColor(this, &FAssetEditorToolkit::GetDefaultTabColor)
.Label(Label)
.LabelOverflowPolicy(FGlobalTabmanager::Get()->GetShouldUseMiddleEllipsisForDockTabLabel() ? ETextOverflowPolicy::MiddleEllipsis : ETextOverflowPolicy::Ellipsis)
.LabelSuffix(LabelSuffix);
const TAttribute<const FSlateBrush*> TabIcon = TAttribute<const FSlateBrush*>::CreateSP(this, &FAssetEditorToolkit::GetDefaultTabIcon);
NewMajorTab->SetTabIcon(TabIcon);
{
static_assert(sizeof(EAssetEditorToolkitTabLocation) == sizeof(int32), "EAssetEditorToolkitTabLocation is the incorrect size");
const UEditorStyleSettings* StyleSettings = GetDefault<UEditorStyleSettings>();
FName PlaceholderId(TEXT("StandaloneToolkit"));
TSharedPtr<FTabManager::FSearchPreference> SearchPreference = nullptr;
if ( StyleSettings->AssetEditorOpenLocation == EAssetEditorOpenLocation::Default )
{
// Work out where we should create this asset editor
EAssetEditorToolkitTabLocation SavedAssetEditorToolkitTabLocation = EAssetEditorToolkitTabLocation::Standalone;
GConfig->GetInt(
TEXT("AssetEditorToolkitTabLocation"),
*ObjectsToEdit[0]->GetPathName(),
reinterpret_cast<int32&>( SavedAssetEditorToolkitTabLocation ),
GEditorPerProjectIni
);
PlaceholderId = ( SavedAssetEditorToolkitTabLocation == EAssetEditorToolkitTabLocation::Docked ) ? TEXT("DockedToolkit") : TEXT("StandaloneToolkit");
SearchPreference = MakeShareable(new FTabManager::FLiveTabSearch());
}
else if ( StyleSettings->AssetEditorOpenLocation == EAssetEditorOpenLocation::NewWindow )
{
PlaceholderId = TEXT("StandaloneToolkit");
SearchPreference = MakeShareable(new FTabManager::FRequireClosedTab());
}
else if ( StyleSettings->AssetEditorOpenLocation == EAssetEditorOpenLocation::MainWindow )
{
PlaceholderId = TEXT("DockedToolkit");
SearchPreference = MakeShareable(new FTabManager::FLiveTabSearch(TEXT("LevelEditor")));
}
else if ( StyleSettings->AssetEditorOpenLocation == EAssetEditorOpenLocation::ContentBrowser )
{
PlaceholderId = TEXT("DockedToolkit");
SearchPreference = MakeShareable(new FTabManager::FLiveTabSearch(TEXT("ContentBrowserTab1")));
}
else if ( StyleSettings->AssetEditorOpenLocation == EAssetEditorOpenLocation::LastDockedWindowOrNewWindow )
{
PlaceholderId = TEXT("StandaloneToolkit");
SearchPreference = MakeShareable(new FTabManager::FLastMajorOrNomadTab(NAME_None));
}
else if ( StyleSettings->AssetEditorOpenLocation == EAssetEditorOpenLocation::LastDockedWindowOrMainWindow )
{
PlaceholderId = TEXT("StandaloneToolkit");
SearchPreference = MakeShareable(new FTabManager::FLastMajorOrNomadTab(TEXT("LevelEditor")));
}
else if ( StyleSettings->AssetEditorOpenLocation == EAssetEditorOpenLocation::LastDockedWindowOrContentBrowser )
{
PlaceholderId = TEXT("StandaloneToolkit");
SearchPreference = MakeShareable(new FTabManager::FLastMajorOrNomadTab(TEXT("ContentBrowserTab1")));
}
else
{
// Add more cases!
check(false);
}
FGlobalTabmanager::Get()->InsertNewDocumentTab(PlaceholderId, *SearchPreference, NewMajorTab.ToSharedRef());
// Bring the window to front. The tab manager will not do this for us to avoid intrusive stealing focus behavior
// However, here the expectation is that opening an new asset editor is something that should steal focus so the user can see their asset
TSharedPtr<SWindow> Window = NewMajorTab->GetParentWindow();
if(Window.IsValid())
{
Window->BringToFront();
}
}
const TSharedRef<FTabManager> NewTabManager = FGlobalTabmanager::Get()->NewTabManager( NewMajorTab.ToSharedRef() );
NewTabManager->SetOnPersistLayout(FTabManager::FOnPersistLayout::CreateRaw(this, &FAssetEditorToolkit::HandleTabManagerPersistLayout));
NewTabManager->SetAllowWindowMenuBar(true);
NewTabManager->SetReadOnly(OpenMethod == EAssetOpenMethod::View);
this->TabManager = NewTabManager;
TArray<TWeakObjectPtr<UObject>> ObjectsToEditWeak;
EVisibility VisibilityWhileCompiling = GetVisibilityWhileAssetCompiling();
ObjectsToEditWeak.Reserve(ObjectsToEdit.Num());
for (UObject* Object : ObjectsToEdit)
{
ObjectsToEditWeak.Add(Object);
}
NewMajorTab->SetContent
(
SAssignNew( NewStandaloneHost, SStandaloneAssetEditorToolkitHost, NewTabManager, AppIdentifier )
.Visibility_Lambda([ObjectsToEditWeak,VisibilityWhileCompiling]()
{
for (const TWeakObjectPtr<UObject> Object : ObjectsToEditWeak)
{
if (const IInterface_AsyncCompilation* AsyncAsset = Cast<IInterface_AsyncCompilation>(Object.Get()))
{
if (AsyncAsset->IsCompiling())
{
return VisibilityWhileCompiling;
}
}
}
return EVisibility::Visible;
})
.OnRequestClose(this, &FAssetEditorToolkit::OnRequestClose, EAssetEditorCloseReason::AssetEditorHostClosed)
.OnClose(this, &FAssetEditorToolkit::OnClose)
);
// Assign our toolkit host before we setup initial content. (Important: We must cache this pointer here as SetupInitialContent
// will callback into the toolkit host.)
ToolkitHost = NewStandaloneHost;
StandaloneHost = NewStandaloneHost;
}
check( ToolkitHost.IsValid() );
ToolkitManager.RegisterNewToolkit( SharedThis( this ) );
MapToolkitCommands();
InitializeReadOnlyMenuProfiles();
// Give a chance to customize tab manager and other UI before widgets are created
GEditor->GetEditorSubsystem<UAssetEditorSubsystem>()->NotifyEditorOpeningPreWidgets(ObjectPtrDecay(EditingObjects), this);
// Create menus
if (ToolkitMode == EToolkitMode::Standalone)
{
AddMenuExtender(GetSharedMenuExtensibilityManager()->GetAllExtenders(ToolkitCommands, ObjectPtrDecay(EditingObjects)));
TSharedRef<FTabManager::FLayout> LayoutToUse = FLayoutSaveRestore::LoadFromConfig(LayoutIni, StandaloneDefaultLayout);
// Actually create the widget content
if (NewStandaloneHost)
{
NewStandaloneHost->SetupInitialContent(LayoutToUse, NewMajorTab, bCreateDefaultStandaloneMenu);
}
}
// Create toolbars
AddToolbarExtender(GetSharedToolBarExtensibilityManager()->GetAllExtenders(ToolkitCommands, ObjectPtrDecay(EditingObjects)));
if (bCreateDefaultToolbar)
{
GenerateToolbar();
}
else
{
Toolbar = SNullWidget::NullWidget;
}
if (NewStandaloneHost)
{
NewStandaloneHost->SetToolbar(Toolbar);
}
// Create our mode manager and set it's toolkit host
if (!EditorModeManager)
{
CreateEditorModeManager();
}
if (EditorModeManager)
{
EditorModeManager->SetToolkitHost(ToolkitHost.Pin().ToSharedRef());
}
PostInitAssetEditor();
// NOTE: Currently, the AssetEditorSubsystem will keep a hard reference to our object as we're editing it
GEditor->GetEditorSubsystem<UAssetEditorSubsystem>()->NotifyAssetsOpened( ObjectPtrDecay(EditingObjects), this );
}
FAssetEditorToolkit::~FAssetEditorToolkit()
{
EditingObjects.Empty();
// We're no longer editing this object, so let the editor know
if (GEditor)
{
if (UAssetEditorSubsystem* AssetEditorSubsystem = GEditor->GetEditorSubsystem<UAssetEditorSubsystem>())
{
AssetEditorSubsystem->NotifyEditorClosed(this);
}
}
EditorModeManager.Reset();
PRAGMA_DISABLE_DEPRECATION_WARNINGS
AssetEditorModeManager = nullptr;
PRAGMA_ENABLE_DEPRECATION_WARNINGS
}
void FAssetEditorToolkit::RegisterTabSpawners(const TSharedRef<class FTabManager>& InTabManager)
{
// Use the first child category of the local workspace root if there is one, otherwise use the root itself
const auto& LocalCategories = InTabManager->GetLocalWorkspaceMenuRoot()->GetChildItems();
AssetEditorTabsCategory = LocalCategories.Num() > 0 ? LocalCategories[0] : InTabManager->GetLocalWorkspaceMenuRoot();
}
void FAssetEditorToolkit::UnregisterTabSpawners(const TSharedRef<class FTabManager>& InTabManager)
{
InTabManager->ClearLocalWorkspaceMenuCategories();
}
bool FAssetEditorToolkit::IsAssetEditor() const
{
return true;
}
FText FAssetEditorToolkit::GetToolkitName() const
{
const UObject* EditingObject = GetEditingObject();
check (EditingObject != NULL);
return GetLabelForObject(EditingObject);
}
FText FAssetEditorToolkit::GetTabSuffix() const
{
bool bDirtyState = false;
for (int32 x = 0; x < EditingObjects.Num(); ++x)
{
if (EditingObjects[x]->GetOutermost()->IsDirty())
{
bDirtyState = true;
break;
}
}
return bDirtyState ? LOCTEXT("TabSuffixAsterix", "*") : FText::GetEmpty();
}
FName FAssetEditorToolkit::GetEditingAssetTypeName() const
{
if(EditingObjects.IsEmpty())
{
return NAME_None;
}
if(UClass* EditingClass = EditingObjects[0]->GetClass())
{
return EditingClass->GetFName();
}
return NAME_None;
}
FText FAssetEditorToolkit::GetToolkitToolTipText() const
{
const UObject* EditingObject = GetEditingObject();
check (EditingObject != NULL);
return GetToolTipTextForObject(EditingObject);
}
FText FAssetEditorToolkit::GetLabelForObject(const UObject* InObject)
{
FString NameString;
if(const AActor* ObjectAsActor = Cast<AActor>(InObject))
{
NameString = ObjectAsActor->GetActorLabel();
}
else
{
NameString = InObject->GetName();
}
return FText::FromString(NameString);
}
FText FAssetEditorToolkit::GetToolTipTextForObject(const UObject* InObject)
{
FString ToolTipString;
if(const AActor* ObjectAsActor = Cast<AActor>(InObject))
{
ToolTipString += LOCTEXT("ToolTipActorLabel", "Actor").ToString();
ToolTipString += TEXT(": ");
ToolTipString += ObjectAsActor->GetActorLabel();
}
else
{
ToolTipString += LOCTEXT("ToolTipAssetLabel", "Asset").ToString();
ToolTipString += TEXT(": ");
ToolTipString += InObject->GetName();
FCollectionManagerModule& CollectionManagerModule = FCollectionManagerModule::GetModule();
TArray<TSharedPtr<ICollectionContainer>> CollectionContainers;
CollectionManagerModule.Get().GetVisibleCollectionContainers(CollectionContainers);
for (const TSharedPtr<ICollectionContainer>& CollectionContainer : CollectionContainers)
{
const FString CollectionNames = CollectionContainer->GetCollectionsStringForObject(FSoftObjectPath(InObject), ECollectionShareType::CST_All);
if (!CollectionNames.IsEmpty())
{
ToolTipString += TEXT("\n");
ToolTipString += CollectionContainer->GetCollectionSource()->GetTitle().ToString();
ToolTipString += TEXT(": ");
ToolTipString += CollectionNames;
}
}
}
return FText::FromString(ToolTipString);
}
FEditorModeTools& FAssetEditorToolkit::GetEditorModeManager() const
{
if (IsWorldCentricAssetEditor() && IsHosted())
{
return GetToolkitHost()->GetEditorModeManager();
}
check(EditorModeManager.IsValid());
return *EditorModeManager.Get();
}
const TArray< UObject* >* FAssetEditorToolkit::GetObjectsCurrentlyBeingEdited() const
{
return &ObjectPtrDecay(EditingObjects);
}
FName FAssetEditorToolkit::GetEditorName() const
{
return GetToolkitFName();
}
void FAssetEditorToolkit::FocusWindow(UObject* ObjectToFocusOn)
{
BringToolkitToFront();
}
bool FAssetEditorToolkit::OnRequestClose(EAssetEditorCloseReason)
{
PRAGMA_DISABLE_DEPRECATION_WARNINGS
return OnRequestClose();
PRAGMA_ENABLE_DEPRECATION_WARNINGS
}
bool FAssetEditorToolkit::CloseWindow()
{
// We use AssetEditorHostClosed as the default close reason for legacy cases
return CloseWindow(EAssetEditorCloseReason::AssetEditorHostClosed);
}
bool FAssetEditorToolkit::CloseWindow(EAssetEditorCloseReason InCloseReason)
{
if (OnRequestClose(InCloseReason))
{
// We are closing, unbind OnRequestClose since we're past that point and we want to make sure we don't redo the request close process when closing the host tab
if (TSharedPtr<SStandaloneAssetEditorToolkitHost> StandaloneHostPtr = StandaloneHost.Pin())
{
StandaloneHostPtr->UnbindEditorCloseRequestFromHostTab();
}
OnClose();
// Close this toolkit
FToolkitManager::Get().CloseToolkit( AsShared() );
}
return true;
}
void FAssetEditorToolkit::InvokeTab(const FTabId& TabId)
{
GetTabManager()->TryInvokeTab(TabId);
}
TSharedPtr<class FTabManager> FAssetEditorToolkit::GetAssociatedTabManager()
{
return TabManager;
}
double FAssetEditorToolkit::GetLastActivationTime()
{
double MostRecentTime = 0.0;
if (TabManager.IsValid())
{
TSharedPtr<SDockTab> OwnerTab = TabManager->GetOwnerTab();
if (OwnerTab.IsValid())
{
MostRecentTime = OwnerTab->GetLastActivationTime();
}
}
return MostRecentTime;
}
TSharedPtr< IToolkitHost > FAssetEditorToolkit::GetPreviousWorldCentricToolkitHost()
{
return PreviousWorldCentricToolkitHost.Pin();
}
void FAssetEditorToolkit::SetPreviousWorldCentricToolkitHostForNewAssetEditor( TSharedRef< IToolkitHost > ToolkitHost )
{
PreviousWorldCentricToolkitHostForNewAssetEditor = ToolkitHost;
}
UObject* FAssetEditorToolkit::GetEditingObject() const
{
check( EditingObjects.Num() == 1 );
return EditingObjects[ 0 ];
}
const TArray< UObject* >& FAssetEditorToolkit::GetEditingObjects() const
{
check( EditingObjects.Num() > 0 );
return ObjectPtrDecay(EditingObjects);
}
TArray<TObjectPtr<UObject>>& FAssetEditorToolkit::GetEditingObjectPtrs()
{
check(EditingObjects.Num() > 0);
return EditingObjects;
}
void FAssetEditorToolkit::GetSaveableObjects(TArray<UObject*>& OutObjects) const
{
for (const TObjectPtr<UObject>& Object : EditingObjects)
{
// If we are editing a subobject of asset (e.g., a level script blueprint which is contained in a map asset), still provide the
// option to work with it but treat save operations/etc... as working on the top level asset itself
for (UObject* TestObject = Object; TestObject != nullptr; TestObject = TestObject->GetOuter())
{
if (TestObject->IsAsset())
{
OutObjects.Add(TestObject);
break;
}
}
}
}
void FAssetEditorToolkit::AddEditingObject(UObject* Object)
{
// Don't allow adding the same object twice (or notify asset opened twice)
if(EditingObjects.Contains(Object))
{
return;
}
EditingObjects.Add(Object);
GEditor->GetEditorSubsystem<UAssetEditorSubsystem>()->NotifyAssetOpened( Object, this );
}
void FAssetEditorToolkit::RemoveEditingObject(UObject* Object)
{
EditingObjects.Remove(Object);
GEditor->GetEditorSubsystem<UAssetEditorSubsystem>()->NotifyAssetClosed( Object, this );
}
bool FAssetEditorToolkit::CanSaveAsset_Internal() const
{
if(GetOpenMethod() != EAssetOpenMethod::Edit)
{
return false;
}
return CanSaveAsset();
}
bool FAssetEditorToolkit::IsSaveAssetVisible() const
{
return true;
}
void FAssetEditorToolkit::SaveAsset_Execute()
{
if (EditingObjects.Num() == 0)
{
return;
}
TArray<UObject*> ObjectsToSave;
GetSaveableObjects(ObjectsToSave);
if (ObjectsToSave.Num() == 0)
{
return;
}
TArray<UObject*> SavedObjects;
SavedObjects.Reserve(ObjectsToSave.Num());
TArray<UPackage*> PackagesToSave;
for (UObject* Object : ObjectsToSave)
{
if ((Object == nullptr) || !Object->IsAsset())
{
// Log an invalid object but don't try to save it
UE_LOG(LogAssetEditorToolkit, Log, TEXT("Invalid object to save: %s"), (Object != nullptr) ? *Object->GetFullName() : TEXT("Null Object"));
}
else
{
PackagesToSave.Add(Object->GetOutermost());
SavedObjects.Add(Object);
}
}
FEditorFileUtils::PromptForCheckoutAndSave(PackagesToSave, bCheckDirtyOnAssetSave, /*bPromptToSave=*/ false);
OnAssetsSaved(SavedObjects);
}
bool FAssetEditorToolkit::CanSaveAssetAs_Internal() const
{
if(GetOpenMethod() != EAssetOpenMethod::Edit)
{
return false;
}
return CanSaveAssetAs();
}
bool FAssetEditorToolkit::IsSaveAssetAsVisible() const
{
return IsActuallyAnAsset();
}
void FAssetEditorToolkit::SaveAssetAs_Execute()
{
if (EditingObjects.Num() == 0)
{
return;
}
TSharedPtr<IToolkitHost> MyToolkitHost = ToolkitHost.Pin();
if (!MyToolkitHost.IsValid())
{
return;
}
// get collection of objects to save
TArray<UObject*> ObjectsToSave;
GetSaveableObjects(ObjectsToSave);
if (ObjectsToSave.Num() == 0)
{
return;
}
// save assets under new name
TArray<UObject*> SavedObjects;
FEditorFileUtils::SaveAssetsAs(ObjectsToSave, SavedObjects);
if (SavedObjects.Num() == 0)
{
return;
}
// close existing asset editors for resaved assets
UAssetEditorSubsystem* AssetEditorSubsystem = GEditor->GetEditorSubsystem<UAssetEditorSubsystem>();
/* @todo editor: Persona does not behave well when closing specific objects
for (int32 Index = 0; Index < ObjectsToSave.Num(); ++Index)
{
if ((SavedObjects[Index] != ObjectsToSave[Index]) && (SavedObjects[Index] != nullptr))
{
AssetEditorSubsystem->CloseAllEditorsForAsset(ObjectsToSave[Index]);
}
}
// reopen asset editor
AssetEditorSubsystem->OpenEditorForAssets(TArrayBuilder<UObject*>().Add(SavedObjects[0]), ToolkitMode, MyToolkitHost.ToSharedRef());
*/
// hack
TArray<UObject*> ObjectsToReopen;
for (auto Object : EditingObjects)
{
if (Object->IsAsset() && !ObjectsToSave.Contains(Object))
{
ObjectsToReopen.Add(Object);
}
}
for (auto Object : SavedObjects)
{
if (ShouldReopenEditorForSavedAsset(Object))
{
ObjectsToReopen.AddUnique(Object);
}
}
for (auto Object : EditingObjects)
{
AssetEditorSubsystem->CloseAllEditorsForAsset(Object);
GEditor->GetEditorSubsystem<UAssetEditorSubsystem>()->NotifyAssetClosed(Object, this);
}
AssetEditorSubsystem->OpenEditorForAssets_Advanced(ObjectsToReopen, ToolkitMode, MyToolkitHost.ToSharedRef());
// end hack
OnAssetsSavedAs(SavedObjects);
}
const FSlateBrush* FAssetEditorToolkit::GetDefaultTabIcon() const
{
if (EditingObjects.Num() == 0)
{
return nullptr;
}
const FSlateBrush* IconBrush = nullptr;
for (UObject* Object : EditingObjects)
{
if (Object)
{
UClass* IconClass = Object->GetClass();
if (IconClass->IsChildOf<UBlueprint>())
{
UBlueprint* Blueprint = Cast<UBlueprint>(Object);
IconClass = Blueprint->GeneratedClass;
}
// Find the first object that has a valid brush
const FSlateBrush* ThisAssetBrush = FSlateIconFinder::FindIconBrushForClass(IconClass);
if (ThisAssetBrush != nullptr)
{
IconBrush = ThisAssetBrush;
break;
}
}
}
if (!IconBrush)
{
IconBrush = FAppStyle::GetBrush(TEXT("ClassIcon.Default"));;
}
return IconBrush;
}
FLinearColor FAssetEditorToolkit::GetDefaultTabColor() const
{
FLinearColor TabColor = FLinearColor::White;
if (EditingObjects.Num() == 0 || !GetDefault<UEditorStyleSettings>()->bEnableColorizedEditorTabs)
{
return TabColor;
}
FAssetToolsModule& AssetToolsModule = FAssetToolsModule::GetModule();
IAssetTools& AssetTools = AssetToolsModule.Get();
for (auto ObjectIt = EditingObjects.CreateConstIterator(); ObjectIt; ++ObjectIt)
{
TWeakPtr<IAssetTypeActions> AssetTypeActions = AssetTools.GetAssetTypeActionsForClass((*ObjectIt)->GetClass());
if (AssetTypeActions.IsValid())
{
const FLinearColor ThisAssetColor = AssetTypeActions.Pin()->GetTypeColor();
if (ThisAssetColor != FLinearColor::Transparent)
{
return ThisAssetColor;
}
}
}
return TabColor;
}
void FAssetEditorToolkit::CreateEditorModeManager()
{
EditorModeManager = MakeShared<FEditorModeTools>();
}
void FAssetEditorToolkit::MapToolkitCommands()
{
ToolkitCommands->MapAction(
FAssetEditorCommonCommands::Get().SaveAsset,
FExecuteAction::CreateSP(this, &FAssetEditorToolkit::SaveAsset_Execute),
FCanExecuteAction::CreateSP(this, &FAssetEditorToolkit::CanSaveAsset_Internal),
FIsActionChecked(),
FIsActionButtonVisible::CreateSP(this, &FAssetEditorToolkit::IsSaveAssetVisible));
ToolkitCommands->MapAction(
FAssetEditorCommonCommands::Get().SaveAssetAs,
FExecuteAction::CreateSP(this, &FAssetEditorToolkit::SaveAssetAs_Execute),
FCanExecuteAction::CreateSP(this, &FAssetEditorToolkit::CanSaveAssetAs_Internal),
FIsActionChecked(),
FIsActionButtonVisible::CreateSP(this, &FAssetEditorToolkit::IsSaveAssetAsVisible));
ToolkitCommands->MapAction(
FGlobalEditorCommonCommands::Get().FindInContentBrowser,
FExecuteAction::CreateSP(this, &FAssetEditorToolkit::FindInContentBrowser_Execute),
FCanExecuteAction::CreateSP(this, &FAssetEditorToolkit::CanFindInContentBrowser),
FIsActionChecked(),
FIsActionButtonVisible::CreateSP(this, &FAssetEditorToolkit::IsFindInContentBrowserButtonVisible));
ToolkitCommands->MapAction(
FGlobalEditorCommonCommands::Get().OpenDocumentation,
FExecuteAction::CreateSP(this, &FAssetEditorToolkit::BrowseDocumentation_Execute));
ToolkitCommands->MapAction(
FAssetEditorCommonCommands::Get().ReimportAsset,
FExecuteAction::CreateSP(this, &FAssetEditorToolkit::Reimport_Execute),
FCanExecuteAction::CreateSP(this, &FAssetEditorToolkit::CanReimport_Internal));
FGlobalEditorCommonCommands::MapActions(ToolkitCommands);
if (IsWorldCentricAssetEditor())
{
ToolkitCommands->MapAction(
FAssetEditorCommonCommands::Get().SwitchToStandaloneEditor,
FExecuteAction::CreateStatic(&FAssetEditorToolkit::SwitchToStandaloneEditor_Execute, TWeakPtr< FAssetEditorToolkit >(AsShared())));
}
else
{
if (GetPreviousWorldCentricToolkitHost().IsValid())
{
ToolkitCommands->MapAction(
FAssetEditorCommonCommands::Get().SwitchToWorldCentricEditor,
FExecuteAction::CreateStatic(&FAssetEditorToolkit::SwitchToWorldCentricEditor_Execute, TWeakPtr< FAssetEditorToolkit >(AsShared())));
}
}
}
void FAssetEditorToolkit::RestoreFromLayout(const TSharedRef<FTabManager::FLayout>& NewLayout, bool bLoadUserLayout)
{
TSharedPtr< class SStandaloneAssetEditorToolkitHost > HostWidget = StandaloneHost.Pin();
if (HostWidget.IsValid())
{
// Save the old layout
FLayoutSaveRestore::SaveToConfig(LayoutIni, TabManager->PersistLayout());
TSharedRef<FTabManager::FLayout> UserConfiguredNewLayout = NewLayout;
if (bLoadUserLayout)
{
// Load the potentially previously saved new layout
UserConfiguredNewLayout = FLayoutSaveRestore::LoadFromConfig(LayoutIni, NewLayout);
}
for (TSharedPtr<FLayoutExtender> LayoutExtender : LayoutExtenders)
{
NewLayout->ProcessExtensions(*LayoutExtender);
}
// Apply the new layout
HostWidget->RestoreFromLayout(UserConfiguredNewLayout);
}
}
FAssetEditorModeManager* FAssetEditorToolkit::GetAssetEditorModeManager() const
{
PRAGMA_DISABLE_DEPRECATION_WARNINGS
return AssetEditorModeManager;
PRAGMA_ENABLE_DEPRECATION_WARNINGS
}
void FAssetEditorToolkit::SetAssetEditorModeManager(FAssetEditorModeManager* InModeManager)
{
PRAGMA_DISABLE_DEPRECATION_WARNINGS
AssetEditorModeManager = InModeManager;
if (AssetEditorModeManager && !AssetEditorModeManager->DoesSharedInstanceExist())
{
EditorModeManager = MakeShareable(AssetEditorModeManager);
}
else
{
EditorModeManager = AssetEditorModeManager->AsShared();
}
PRAGMA_ENABLE_DEPRECATION_WARNINGS
}
void FAssetEditorToolkit::RemoveEditingAsset(UObject* Asset)
{
// Just close the editor tab if it's the last element
if (EditingObjects.Num() == 1 && EditingObjects.Contains(Asset))
{
CloseWindow(EAssetEditorCloseReason::AssetUnloadingOrInvalid);
}
else
{
RemoveEditingObject(Asset);
}
}
void FAssetEditorToolkit::SwitchToStandaloneEditor_Execute( TWeakPtr< FAssetEditorToolkit > ThisToolkitWeakRef )
{
// NOTE: We're being very careful here with pointer handling because we need to make sure the toolkit's
// destructor is called when we call CloseToolkit, as it needs to be fully unregistered before we go
// and try to open a new asset editor for the same asset
// First, close the world-centric toolkit
TArray< FWeakObjectPtr > ObjectsToEditStandaloneWeak;
TSharedPtr< IToolkitHost > PreviousWorldCentricToolkitHost;
{
TSharedRef< FAssetEditorToolkit > ThisToolkit = ThisToolkitWeakRef.Pin().ToSharedRef();
check( ThisToolkit->IsWorldCentricAssetEditor() );
PreviousWorldCentricToolkitHost = ThisToolkit->GetToolkitHost();
const auto& EditingObjects = *ThisToolkit->GetObjectsCurrentlyBeingEdited();
for( auto ObjectIter = EditingObjects.CreateConstIterator(); ObjectIter; ++ObjectIter )
{
ObjectsToEditStandaloneWeak.Add( *ObjectIter );
}
FToolkitManager::Get().CloseToolkit( ThisToolkit );
// At this point, we should be the only referencer of the toolkit! It will be fully destroyed when
// as the code pointer exits this block.
ensure( ThisToolkit.IsUnique() );
}
// Now, reopen the toolkit in "standalone" mode
TArray< UObject* > ObjectsToEdit;
for( auto ObjectPtrItr = ObjectsToEditStandaloneWeak.CreateIterator(); ObjectPtrItr; ++ObjectPtrItr )
{
const auto WeakObjectPtr = *ObjectPtrItr;
if( WeakObjectPtr.IsValid() )
{
ObjectsToEdit.Add( WeakObjectPtr.Get() );
}
}
if( ObjectsToEdit.Num() > 0 )
{
ensure( GEditor->GetEditorSubsystem<UAssetEditorSubsystem>()->OpenEditorForAssets_Advanced( ObjectsToEdit, EToolkitMode::Standalone, PreviousWorldCentricToolkitHost.ToSharedRef() ) );
}
}
void FAssetEditorToolkit::SwitchToWorldCentricEditor_Execute( TWeakPtr< FAssetEditorToolkit > ThisToolkitWeakRef )
{
// @todo toolkit minor: Maybe also allow the user to drag and drop the standalone editor's tab into a specific level editor to switch to world-centric mode?
// NOTE: We're being very careful here with pointer handling because we need to make sure the tookit's
// destructor is called when we call CloseToolkit, as it needs to be fully unregistered before we go
// and try to open a new asset editor for the same asset
// First, close the standalone toolkit
TArray< FWeakObjectPtr > ObjectToEditWorldCentricWeak;
TSharedPtr< IToolkitHost > WorldCentricLevelEditor;
{
TSharedRef< FAssetEditorToolkit > ThisToolkit = ThisToolkitWeakRef.Pin().ToSharedRef();
const auto& EditingObjects = *ThisToolkit->GetObjectsCurrentlyBeingEdited();
for( auto ObjectIter = EditingObjects.CreateConstIterator(); ObjectIter; ++ObjectIter )
{
ObjectToEditWorldCentricWeak.Add( *ObjectIter );
}
check( !ThisToolkit->IsWorldCentricAssetEditor() );
WorldCentricLevelEditor = ThisToolkit->GetPreviousWorldCentricToolkitHost();
FToolkitManager::Get().CloseToolkit( ThisToolkit );
// At this point, we should be the only referencer of the toolkit! It will be fully destroyed when
// as the code pointer exits this block.
ensure( ThisToolkit.IsUnique() );
}
// Now, reopen the toolkit in "world-centric" mode
TArray< UObject* > ObjectsToEdit;
for( auto ObjectPtrItr = ObjectToEditWorldCentricWeak.CreateIterator(); ObjectPtrItr; ++ObjectPtrItr )
{
const auto WeakObjectPtr = *ObjectPtrItr;
if( WeakObjectPtr.IsValid() )
{
ObjectsToEdit.Add( WeakObjectPtr.Get() );
}
}
if( ObjectsToEdit.Num() > 0 )
{
ensure( GEditor->GetEditorSubsystem<UAssetEditorSubsystem>()->OpenEditorForAssets_Advanced( ObjectsToEdit, EToolkitMode::WorldCentric, WorldCentricLevelEditor ) );
}
}
EVisibility FAssetEditorToolkit::GetVisibilityWhileAssetCompiling() const
{
// don't tick GUI of asset editor when asset is compiling
// this is to prevent deadlocks in broken asset editors
// @todo : change this default to Visible and instead return Collapsed only in the editors that need this bodge
return EVisibility::Collapsed;
}
void FAssetEditorToolkit::FindInContentBrowser_Execute()
{
TArray< UObject* > ObjectsToSyncTo;
GetSaveableObjects(ObjectsToSyncTo);
if (ObjectsToSyncTo.Num() > 0)
{
GEditor->SyncBrowserToObjects( ObjectsToSyncTo );
}
}
void FAssetEditorToolkit::BrowseDocumentation_Execute() const
{
IDocumentation::Get()->Open(GetDocumentationLink(), FDocumentationSourceInfo(TEXT("help_menu_asset")));
}
FString FAssetEditorToolkit::GetDocumentationLink() const
{
return FString(TEXT("%ROOT%"));
}
bool FAssetEditorToolkit::CanReimport() const
{
for( auto ObjectIter = EditingObjects.CreateConstIterator(); ObjectIter; ++ObjectIter )
{
auto EditingObject = *ObjectIter;
if ( CanReimport( EditingObject ) )
{
return true;
}
}
return false;
}
bool FAssetEditorToolkit::CanReimport_Internal() const
{
if(GetOpenMethod() != EAssetOpenMethod::Edit)
{
return false;
}
return CanReimport();
}
bool FAssetEditorToolkit::CanReimport( UObject* EditingObject ) const
{
// Don't allow user to perform certain actions on objects that aren't actually assets (e.g. Level Script blueprint objects)
if( EditingObject != NULL && EditingObject->IsAsset() )
{
// Apply the same logic as Reimport from the Context Menu, see FAssetFileContextMenu::AreImportedAssetActionsVisible
FAssetToolsModule& AssetToolsModule = FModuleManager::Get().LoadModuleChecked<FAssetToolsModule>("AssetTools");
UClass* EditingClass = EditingObject->GetClass();
auto AssetTypeActions = AssetToolsModule.Get().GetAssetTypeActionsForClass(EditingClass).Pin();
if(!AssetTypeActions.IsValid() || !AssetTypeActions->IsImportedAsset())
{
return false;
}
if ( FReimportManager::Instance()->CanReimport( EditingObject ) )
{
return true;
}
}
return false;
}
void FAssetEditorToolkit::Reimport_Execute()
{
if( ensure( EditingObjects.Num() > 0 ) )
{
for( auto ObjectIter = EditingObjects.CreateConstIterator(); ObjectIter; ++ObjectIter )
{
const auto EditingObject = *ObjectIter;
Reimport_Execute( EditingObject );
}
}
}
void FAssetEditorToolkit::Reimport_Execute( UObject* EditingObject )
{
// Don't allow user to perform certain actions on objects that aren't actually assets (e.g. Level Script blueprint objects)
if( EditingObject != NULL && EditingObject->IsAsset() )
{
// Reimport the asset
FReimportManager::Instance()->Reimport(EditingObject, ShouldPromptForNewFilesOnReload(*EditingObject));
}
}
bool FAssetEditorToolkit::ShouldPromptForNewFilesOnReload(const UObject& EditingObject) const
{
return true;
}
void FAssetEditorToolkit::FillDefaultFileMenuOpenCommands(FToolMenuSection& InSection)
{
GEditor->GetEditorSubsystem<UAssetEditorSubsystem>()->CreateRecentAssetsMenuForEditor(this, InSection);
}
void FAssetEditorToolkit::FillDefaultFileMenuCommands(FToolMenuSection& InSection)
{
const FToolMenuInsert InsertPosition(NAME_None, EToolMenuInsertType::First);
if (UAssetEditorToolkitMenuContext* Context = InSection.FindContext<UAssetEditorToolkitMenuContext>())
{
InSection.AddMenuEntry(FAssetEditorCommonCommands::Get().SaveAsset, TAttribute<FText>(), TAttribute<FText>(), FSlateIcon(FAppStyle::GetAppStyleSetName(), "AssetEditor.SaveAsset")).InsertPosition = InsertPosition;
InSection.AddMenuEntry(FAssetEditorCommonCommands::Get().SaveAssetAs, TAttribute<FText>(), TAttribute<FText>(), FSlateIcon(FAppStyle::GetAppStyleSetName(), "AssetEditor.SaveAssetAs")).InsertPosition = InsertPosition;
}
if( IsWorldCentricAssetEditor() )
{
// @todo toolkit minor: It would be awesome if the user could just "tear off" the SToolkitDisplay to do SwitchToStandaloneEditor
// Would need to probably drop at mouseup location though instead of using saved layout pos.
InSection.AddMenuEntry( FAssetEditorCommonCommands::Get().SwitchToStandaloneEditor ).InsertPosition = InsertPosition;;
}
else
{
if( GetPreviousWorldCentricToolkitHost().IsValid() )
{
// @todo toolkit checkin: Disabled temporarily until we have world-centric "ready to use"!
if( 0 )
{
InSection.AddMenuEntry( FAssetEditorCommonCommands::Get().SwitchToWorldCentricEditor ).InsertPosition = InsertPosition;;
}
}
}
}
void FAssetEditorToolkit::FillDefaultAssetMenuCommands(FToolMenuSection& InSection)
{
InSection.AddMenuEntry(FGlobalEditorCommonCommands::Get().FindInContentBrowser, LOCTEXT("FindInContentBrowser", "Find in Content Browser..."));
// Commands we only want to be accessible when editing an asset should go here
if( IsActuallyAnAsset() )
{
FName ReimportEntryName = TEXT("Reimport");
int32 MenuEntryCount = 0;
// Add a reimport menu entry for each supported editable object
for( auto ObjectIter = EditingObjects.CreateConstIterator(); ObjectIter; ++ObjectIter )
{
const auto EditingObject = *ObjectIter;
if( EditingObject != NULL && EditingObject->IsAsset() )
{
if ( CanReimport( EditingObject ) )
{
FFormatNamedArguments LabelArguments;
LabelArguments.Add(TEXT("Name"), FText::FromString( EditingObject->GetName() ));
const FText LabelText = FText::Format( LOCTEXT("Reimport_Label", "Reimport {Name}..."), LabelArguments );
FFormatNamedArguments ToolTipArguments;
ToolTipArguments.Add(TEXT("Type"), FText::FromString( EditingObject->GetClass()->GetName() ));
const FText ToolTipText = FText::Format( LOCTEXT("Reimport_ToolTip", "Reimports this {Type}"), ToolTipArguments );
const FName IconName = TEXT( "AssetEditor.ReimportAsset" );
FUIAction UIAction;
UIAction.ExecuteAction.BindRaw( this, &FAssetEditorToolkit::Reimport_Execute, EditingObject.Get() );
ReimportEntryName.SetNumber(MenuEntryCount++);
InSection.AddMenuEntry( ReimportEntryName, LabelText, ToolTipText, FSlateIcon(FAppStyle::GetAppStyleSetName(), IconName), UIAction );
}
}
}
}
}
void FAssetEditorToolkit::FillDefaultHelpMenuCommands(FToolMenuSection& InSection)
{
// Only show the documentation menu item if the asset editor has specified a resource.
FString DocLink = GetDocumentationLink();
if (DocLink != "%ROOT%")
{
FFormatNamedArguments Args;
Args.Add(TEXT("Editor"), GetBaseToolkitName());
const FText ToolTip = FText::Format(LOCTEXT("BrowseDocumentationTooltip", "Details on using the {Editor}"), Args);
InSection.AddMenuEntry(FGlobalEditorCommonCommands::Get().OpenDocumentation, FText::Format(LOCTEXT("AssetEditorDocumentationMenuLabel", "{Editor} Documentation"), Args), ToolTip);
}
}
FName FAssetEditorToolkit::GetToolMenuAppName() const
{
if (IsSimpleAssetEditor() && EditingObjects.Num() == 1 && EditingObjects[0])
{
return *(EditingObjects[0]->GetClass()->GetFName().ToString() + TEXT("Editor"));
}
return GetToolkitFName();
}
FName FAssetEditorToolkit::GetToolMenuName() const
{
return *(TEXT("AssetEditor.") + GetToolMenuAppName().ToString() + TEXT(".MainMenu"));
}
FName FAssetEditorToolkit::GetToolMenuToolbarName() const
{
FName ParentName;
return GetToolMenuToolbarName(ParentName);
}
FName FAssetEditorToolkit::GetToolMenuToolbarName(FName& OutParentName) const
{
OutParentName = DefaultAssetEditorToolBarName;
return *(TEXT("AssetEditor.") + GetToolMenuAppName().ToString() + TEXT(".ToolBar"));
}
void FAssetEditorToolkit::RegisterDefaultToolBar()
{
UToolMenus* ToolMenus = UToolMenus::Get();
if (!ToolMenus->IsMenuRegistered(DefaultAssetEditorToolBarName))
{
UToolMenu* ToolbarBuilder = ToolMenus->RegisterMenu(DefaultAssetEditorToolBarName, NAME_None, EMultiBoxType::SlimHorizontalToolBar);
FToolMenuSection& Section = ToolbarBuilder->AddSection("Asset");
}
}
void FAssetEditorToolkit::InitializeReadOnlyMenuProfiles()
{
// Toolbar Customizations
// Only allow the "Find in Content Browser" toolbar item by default - which hides "Save" since we are using an allowlist
ReadOnlyCustomization.ToolbarPermissionList.AddAllowListItem(ReadOnlyMenuProfileName, FGlobalEditorCommonCommands::Get().FindInContentBrowser->GetCommandName());
// Main Menu Customizations
// The default menus we provide in the main menu
TArray<FName> MainMenuSubmenus({"File", "Edit", "Asset", "Window", "Tools", "Help"});
// Only allow the default menus we provide
for(const FName& Submenu : MainMenuSubmenus)
{
ReadOnlyCustomization.MainMenuPermissionList.AddAllowListItem(ReadOnlyMenuProfileName, Submenu);
ReadOnlyCustomization.MainMenuSubmenuPermissionLists.Add(Submenu, FNamePermissionList());
}
// Hide the save commands in the "File" menu in read only mode
FNamePermissionList& FileMenuPermissionList = ReadOnlyCustomization.MainMenuSubmenuPermissionLists.FindOrAdd("File");
FileMenuPermissionList.AddDenyListItem(ReadOnlyMenuProfileName, FAssetEditorCommonCommands::Get().SaveAsset->GetCommandName());
FileMenuPermissionList.AddDenyListItem(ReadOnlyMenuProfileName, FAssetEditorCommonCommands::Get().SaveAssetAs->GetCommandName());
// Hide the re-import command in the "asset" menu in read only mode
// There is a reimport command per editing object, so we make sure to hide them all. See FAssetEditorToolkit::FillDefaultAssetMenuCommands
FNamePermissionList& AssetMenuPermissionList = ReadOnlyCustomization.MainMenuSubmenuPermissionLists.FindOrAdd("Asset");
FName ReimportEntryName = TEXT("Reimport");
int32 MenuEntryCount = 0;
for( auto ObjectIter = EditingObjects.CreateConstIterator(); ObjectIter; ++ObjectIter )
{
ReimportEntryName.SetNumber(MenuEntryCount++);
AssetMenuPermissionList.AddDenyListItem(ReadOnlyMenuProfileName, ReimportEntryName);
}
// Give specific asset editors a chance to customize the default behavior (e.g show specific menus they want to allow in read only mode)
SetupReadOnlyMenuProfiles(ReadOnlyCustomization);
// Create the menu profile and apply the permission lists
FToolMenuProfile* MainMenuProfile = UToolMenus::Get()->AddRuntimeMenuProfile(GetToolMenuName(), ReadOnlyMenuProfileName);
FToolMenuProfile* ToolbarProfile = UToolMenus::Get()->AddRuntimeMenuProfile(GetToolMenuToolbarName(), ReadOnlyMenuProfileName);
FToolMenuProfile* CommonActionsToolbarProfile = UToolMenus::Get()->AddRuntimeMenuProfile("AssetEditorToolbar.CommonActions", ReadOnlyMenuProfileName);
MainMenuProfile->MenuPermissions = ReadOnlyCustomization.MainMenuPermissionList;
ToolbarProfile->MenuPermissions = ReadOnlyCustomization.ToolbarPermissionList;
CommonActionsToolbarProfile->MenuPermissions = ReadOnlyCustomization.ToolbarPermissionList;
for(const FName& Submenu : MainMenuSubmenus)
{
const FName SubmenuName = *(GetToolMenuName().ToString() + TEXT(".") + Submenu.ToString());
FToolMenuProfile* SubmenuProfile = UToolMenus::Get()->AddRuntimeMenuProfile(SubmenuName, ReadOnlyMenuProfileName);
SubmenuProfile->MenuPermissions = ReadOnlyCustomization.MainMenuSubmenuPermissionLists[Submenu];
}
}
void FAssetEditorToolkit::InitToolMenuContext(FToolMenuContext& MenuContext)
{
UAssetEditorToolkitMenuContext* ToolkitMenuContext = MenuContext.FindContext<UAssetEditorToolkitMenuContext>();
if(!ToolkitMenuContext || !ToolkitMenuContext->Toolkit.IsValid())
{
return;
}
// If we are in read only mode, set the read only menu profile as active
if(ToolkitMenuContext->Toolkit.Pin()->GetOpenMethod() == EAssetOpenMethod::View)
{
UToolMenuProfileContext* ProfileContext = NewObject<UToolMenuProfileContext>();
ProfileContext->ActiveProfiles.Add(ReadOnlyMenuProfileName);
MenuContext.AddObject(ProfileContext);
}
}
UToolMenu* FAssetEditorToolkit::GenerateCommonActionsToolbar(FToolMenuContext& MenuContext)
{
UToolMenus* ToolMenus = UToolMenus::Get();
FName ToolBarName = "AssetEditorToolbar.CommonActions";
UToolMenu* FoundMenu = ToolMenus->FindMenu(ToolBarName);
if (!FoundMenu || !FoundMenu->IsRegistered())
{
FoundMenu = ToolMenus->RegisterMenu(ToolBarName, NAME_None, EMultiBoxType::SlimHorizontalToolBar);
FoundMenu->StyleName = "AssetEditorToolbar";
FToolMenuSection& Section = FoundMenu->AddSection("CommonActions");
Section.AddDynamicEntry("CommonActionsDynamic", FNewToolMenuSectionDelegate::CreateLambda([this](FToolMenuSection& InSection)
{
UAssetEditorToolkitMenuContext* AssetEditorToolkitMenuContext = InSection.FindContext<UAssetEditorToolkitMenuContext>();
if (AssetEditorToolkitMenuContext)
{
if(TSharedPtr<FAssetEditorToolkit> AssetEditorToolkit = AssetEditorToolkitMenuContext->Toolkit.Pin())
{
InSection.AddEntry(FToolMenuEntry::InitToolBarButton(FAssetEditorCommonCommands::Get().SaveAsset));
}
}
}));
Section.AddEntry(FToolMenuEntry::InitToolBarButton(FGlobalEditorCommonCommands::Get().FindInContentBrowser, LOCTEXT("FindInContentBrowserButton", "Browse")));
Section.AddSeparator(NAME_None);
}
return ToolMenus->GenerateMenu(ToolBarName, MenuContext);
}
UToolMenu* FAssetEditorToolkit::GenerateReadOnlyToolbar(FToolMenuContext& MenuContext)
{
UToolMenus* ToolMenus = UToolMenus::Get();
FName ToolBarName = "AssetEditorToolbar.ReadOnly";
UToolMenu* FoundMenu = ToolMenus->FindMenu(ToolBarName);
if (!FoundMenu || !FoundMenu->IsRegistered())
{
FoundMenu = ToolMenus->RegisterMenu(ToolBarName, NAME_None, EMultiBoxType::SlimHorizontalToolBar);
FoundMenu->StyleName = "AssetEditorToolbar";
FToolMenuSection& Section = FoundMenu->AddSection("ReadOnly");
Section.AddDynamicEntry("ReadOnlyDynamic", FNewToolMenuSectionDelegate::CreateLambda([this](FToolMenuSection& InSection)
{
// A custom widget to show the read only status of the asset editor
TSharedRef<SWidget> ReadOnlyIndicatorWidget =
SNew(SHorizontalBox)
+SHorizontalBox::Slot()
.Padding(20.0f, 0.0f)
[
SNew(SBorder)
.BorderImage(FAppStyle::GetBrush("AssetEditor.ReadOnlyBorder"))
.Padding(0.0f, 0.0f)
[
SNew(SHorizontalBox)
+SHorizontalBox::Slot()
.AutoWidth()
.HAlign(HAlign_Left)
.VAlign(VAlign_Center)
.Padding(8.0f, 0.0f, 0.0f, 0.0f)
[
SNew(SBox)
.HeightOverride(16.0f)
.WidthOverride(16.0f)
[
SNew(SImage)
.Image(FAppStyle::GetBrush("AssetEditor.ReadOnlyOpenable"))
.ColorAndOpacity(FStyleColors::AccentBlack)
]
]
+SHorizontalBox::Slot()
.HAlign(HAlign_Left)
.VAlign(VAlign_Center)
.Padding(4.0f, 0.0f, 8.0f, 0.0f)
[
SNew(STextBlock)
.Text(LOCTEXT("ReadOnlyText", "Read Only"))
.ColorAndOpacity(FStyleColors::AccentBlack)
]
]
];
InSection.AddSeparator(NAME_None);
InSection.AddEntry(FToolMenuEntry::InitWidget("ReadOnlyIndicatorWidget", ReadOnlyIndicatorWidget, FText::GetEmpty()));
}));
}
return ToolMenus->GenerateMenu(ToolBarName, MenuContext);;
}
void FAssetEditorToolkit::GenerateToolbar()
{
TSharedPtr<FExtender> Extender = FExtender::Combine(ToolbarExtenders);
RegisterDefaultToolBar();
FName ParentToolbarName;
const FName ToolBarName = GetToolMenuToolbarName(ParentToolbarName);
UToolMenus* ToolMenus = UToolMenus::Get();
UToolMenu* FoundMenu = ToolMenus->FindMenu(ToolBarName);
if (!FoundMenu || !FoundMenu->IsRegistered())
{
FoundMenu = ToolMenus->RegisterMenu(ToolBarName, ParentToolbarName, EMultiBoxType::SlimHorizontalToolBar);
}
FToolMenuContext MenuContext(GetToolkitCommands(), Extender);
UAssetEditorToolkitMenuContext* ToolkitMenuContext = NewObject<UAssetEditorToolkitMenuContext>(FoundMenu);
ToolkitMenuContext->Toolkit = AsShared();
MenuContext.AddObject(ToolkitMenuContext);
InitToolMenuContext(MenuContext);
UToolMenu* GeneratedToolbar = ToolMenus->GenerateMenu(ToolBarName, MenuContext);
GeneratedToolbar->bToolBarIsFocusable = bIsToolbarFocusable;
GeneratedToolbar->bToolBarForceSmallIcons = bIsToolbarUsingSmallIcons;
TSharedRef< class SWidget > ToolBarWidget = ToolMenus->GenerateWidget(GeneratedToolbar);
UToolMenu* CommonActionsToolbar = GenerateCommonActionsToolbar(MenuContext);
TSharedRef< class SWidget > CommonActionsToolbarWidget = ToolMenus->GenerateWidget(CommonActionsToolbar);
UToolMenu* ReadOnlyToolbar = GenerateReadOnlyToolbar(MenuContext);
TSharedRef< class SWidget > ReadOnlyWidget = ToolMenus->GenerateWidget(ReadOnlyToolbar);
TSharedRef<SWidget> MiscWidgets = SNullWidget::NullWidget;
if(ToolbarWidgets.Num() > 0)
{
TSharedRef<SHorizontalBox> MiscWidgetsHBox = SNew(SHorizontalBox);
for (int32 WidgetIdx = 0; WidgetIdx < ToolbarWidgets.Num(); ++WidgetIdx)
{
MiscWidgetsHBox->AddSlot()
.AutoWidth()
.VAlign(VAlign_Center)
[
ToolbarWidgets[WidgetIdx]
];
}
MiscWidgets = MiscWidgetsHBox;
}
Toolbar =
SNew(SHorizontalBox)
+SHorizontalBox::Slot()
.AutoWidth()
[
CommonActionsToolbarWidget
]
+SHorizontalBox::Slot()
[
SNew(SBorder)
.VAlign(VAlign_Center)
.BorderImage(FAppStyle::Get().GetBrush("AssetEditorToolbar.Background"))
.Padding(FMargin(0.0f))
[
ToolBarWidget
]
]
+SHorizontalBox::Slot()
.HAlign(HAlign_Right)
.AutoWidth()
[
SNew(SBorder)
.VAlign(VAlign_Center)
.BorderImage(FAppStyle::Get().GetBrush("AssetEditorToolbar.Background"))
.Padding(MiscWidgets == SNullWidget::NullWidget ? FMargin(0.0f) : FMargin(4.f, 0.f, 0.f, 0.f))
[
MiscWidgets
]
]
+SHorizontalBox::Slot()
.HAlign(HAlign_Right)
.AutoWidth()
[
SNew(SBorder)
.VAlign(VAlign_Center)
.Visibility_Lambda([this]()
{
return GetOpenMethod() == EAssetOpenMethod::View ? EVisibility::Visible : EVisibility::Collapsed;
})
.BorderImage(FAppStyle::Get().GetBrush("AssetEditorToolbar.Background"))
.Padding(FMargin(0.0f))
[
ReadOnlyWidget
]
];
if (ToolbarWidgetContent.IsValid())
{
ToolbarWidgetContent->SetContent(Toolbar.ToSharedRef());
}
}
void FAssetEditorToolkit::RegenerateMenusAndToolbars()
{
RemoveAllToolbarWidgets();
TSharedPtr<SStandaloneAssetEditorToolkitHost> HostWidget = StandaloneHost.Pin();
if (HostWidget)
{
HostWidget->GenerateMenus(false);
}
if (Toolbar != SNullWidget::NullWidget)
{
GenerateToolbar();
}
PostRegenerateMenusAndToolbars();
if (HostWidget)
{
HostWidget->SetToolbar(Toolbar);
}
}
void FAssetEditorToolkit::RegisterDrawer(FWidgetDrawerConfig&& Drawer, int32 SlotIndex)
{
TSharedPtr< class SStandaloneAssetEditorToolkitHost > HostWidget = StandaloneHost.Pin();
if (HostWidget.IsValid())
{
HostWidget->RegisterDrawer(MoveTemp(Drawer), SlotIndex);
}
}
void FAssetEditorToolkit::RestoreFromLayout(const TSharedRef<FTabManager::FLayout>& NewLayout)
{
constexpr bool bLoadUserLayout = true;
RestoreFromLayout(NewLayout, bLoadUserLayout);
}
bool FAssetEditorToolkit::IsActuallyAnAsset() const
{
// Don't allow user to perform certain actions on objects that aren't actually assets (e.g. Level Script blueprint objects)
bool bIsActuallyAnAsset = false;
for( auto ObjectIter = GetObjectsCurrentlyBeingEdited()->CreateConstIterator(); !bIsActuallyAnAsset && ObjectIter; ++ObjectIter )
{
const auto ObjectBeingEdited = *ObjectIter;
bIsActuallyAnAsset |= ObjectBeingEdited != NULL && ObjectBeingEdited->IsAsset();
}
return bIsActuallyAnAsset;
}
UE::Editor::Toolbars::ECreateStatusBarOptions FAssetEditorToolkit::GetStatusBarCreationOptions() const
{
return UE::Editor::Toolbars::ECreateStatusBarOptions::Default;
}
void FAssetEditorToolkit::AddMenuExtender(TSharedPtr<FExtender> Extender)
{
if (TSharedPtr<SStandaloneAssetEditorToolkitHost> StandaloneHostPtr = StandaloneHost.Pin())
{
StandaloneHostPtr->GetMenuExtenders().AddUnique(Extender);
}
}
void FAssetEditorToolkit::RemoveMenuExtender(TSharedPtr<FExtender> Extender)
{
if (TSharedPtr<SStandaloneAssetEditorToolkitHost> StandaloneHostPtr = StandaloneHost.Pin())
{
StandaloneHostPtr->GetMenuExtenders().Remove(Extender);
}
}
void FAssetEditorToolkit::AddToolbarExtender(TSharedPtr<FExtender> Extender)
{
ToolbarExtenders.AddUnique(Extender);
}
void FAssetEditorToolkit::RemoveToolbarExtender(TSharedPtr<FExtender> Extender)
{
ToolbarExtenders.Remove(Extender);
}
TSharedPtr<FExtensibilityManager> FAssetEditorToolkit::GetSharedMenuExtensibilityManager()
{
if (!SharedMenuExtensibilityManager.IsValid())
{
SharedMenuExtensibilityManager = MakeShareable(new FExtensibilityManager);
}
return SharedMenuExtensibilityManager;
}
TSharedPtr<FExtensibilityManager> FAssetEditorToolkit::GetSharedToolBarExtensibilityManager()
{
if (!SharedToolBarExtensibilityManager.IsValid())
{
SharedToolBarExtensibilityManager = MakeShareable(new FExtensibilityManager);
}
return SharedToolBarExtensibilityManager;
}
void FAssetEditorToolkit::SetMenuOverlay( TSharedRef<SWidget> Widget )
{
if (TSharedPtr<SStandaloneAssetEditorToolkitHost> StandaloneHostPtr = StandaloneHost.Pin())
{
StandaloneHostPtr->SetMenuOverlay(Widget);
}
}
void FAssetEditorToolkit::AddToolbarWidget(TSharedRef<SWidget> Widget)
{
ToolbarWidgets.AddUnique(Widget);
}
void FAssetEditorToolkit::RemoveAllToolbarWidgets()
{
ToolbarWidgets.Empty();
}
void FAssetEditorToolkit::FGCEditingObjects::AddReferencedObjects(FReferenceCollector& Collector)
{
Collector.AddReferencedObjects(OwnerToolkit.EditingObjects);
// Remove null objects as a safe guard against assets being forcibly GC'd
OwnerToolkit.EditingObjects.RemoveAllSwap([](UObject* Obj) { return Obj == nullptr; } );
}
FString FAssetEditorToolkit::FGCEditingObjects::GetReferencerName() const
{
return TEXT("FAssetEditorToolkit::FGCEditorObjects");
}
TSharedPtr<FExtender> FExtensibilityManager::GetAllExtenders()
{
return FExtender::Combine(Extenders);
}
TSharedPtr<FExtender> FExtensibilityManager::GetAllExtenders(const TSharedRef<FUICommandList>& CommandList, const TArray<UObject*>& ContextSensitiveObjects)
{
auto OutExtenders = Extenders;
for (int32 i = 0; i < ExtenderDelegates.Num(); ++i)
{
if (ExtenderDelegates[i].IsBound())
{
OutExtenders.Add(ExtenderDelegates[i].Execute(CommandList, ContextSensitiveObjects));
}
}
return FExtender::Combine(OutExtenders);
}
TArray<UObject*> UAssetEditorToolkitMenuContext::GetEditingObjects() const
{
TArray<UObject*> Result;
if (TSharedPtr<FAssetEditorToolkit> Pinned = Toolkit.Pin())
{
for (UObject* Object : Pinned->GetEditingObjects())
{
if (Object)
{
Result.Add(Object);
}
}
}
return Result;
}
#undef LOCTEXT_NAMESPACE