Files
UnrealEngine/Engine/Source/Runtime/Slate/Private/Framework/Docking/TabManager.cpp
2025-05-18 13:04:45 +08:00

3079 lines
95 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "Framework/Docking/TabManager.h"
#include "Dom/JsonValue.h"
#include "Dom/JsonObject.h"
#include "Framework/Commands/UIAction.h"
#include "Widgets/Text/STextBlock.h"
#include "Framework/MultiBox/MultiBoxBuilder.h"
#include "Serialization/JsonReader.h"
#include "Serialization/JsonSerializer.h"
#include "Layout/WidgetPath.h"
#include "Framework/Application/SlateApplication.h"
#include "Widgets/Layout/SBox.h"
#include "Framework/Docking/SDockingNode.h"
#include "Framework/Docking/SDockingSplitter.h"
#include "Framework/Docking/SDockingArea.h"
#include "Widgets/Docking/SDockTab.h"
#include "Framework/Docking/SDockingTabStack.h"
#include "Framework/Docking/SDockingTabWell.h"
#include "Framework/Docking/LayoutExtender.h"
#include "Misc/NamePermissionList.h"
#include "Trace/SlateMemoryTags.h"
#include "HAL/PlatformApplicationMisc.h"
#if PLATFORM_MAC
#include "Framework/MultiBox/Mac/MacMenu.h"
#endif
const FVector2D FTabManager::FallbackWindowSize( 1000, 600 );
TMap<FTabId, FVector2D> FTabManager::DefaultTabWindowSizeMap;
DEFINE_LOG_CATEGORY_STATIC(LogTabManager, Display, All);
#define LOCTEXT_NAMESPACE "TabManager"
static const FString UE_TABMANAGER_OPENED_TAB_STRING = TEXT("OpenedTab");
static const FString UE_TABMANAGER_CLOSED_TAB_STRING = TEXT("ClosedTab");
static const FString UE_TABMANAGER_SIDEBAR_TAB_STRING = TEXT("SidebarTab");
static const FString UE_TABMANAGER_INVALID_TAB_STRING = TEXT("InvalidTab");
static FString StringFromTabState(ETabState::Type TabState)
{
switch (TabState)
{
case ETabState::OpenedTab:
return UE_TABMANAGER_OPENED_TAB_STRING;
case ETabState::ClosedTab:
return UE_TABMANAGER_CLOSED_TAB_STRING;
case ETabState::SidebarTab:
return UE_TABMANAGER_SIDEBAR_TAB_STRING;
default:
return UE_TABMANAGER_INVALID_TAB_STRING;
}
}
static FString StringFromSidebarLocation(ESidebarLocation Location)
{
switch (Location)
{
case ESidebarLocation::Left:
return TEXT("Left");
case ESidebarLocation::Right:
return TEXT("Right");
default:
return TEXT("None");
}
}
static ESidebarLocation SidebarLocationFromString(const FString& AsString)
{
if (AsString == TEXT("Left"))
{
return ESidebarLocation::Left;
}
else if (AsString == TEXT("Right"))
{
return ESidebarLocation::Right;
}
else
{
return ESidebarLocation::None;
}
}
static ETabState::Type TabStateFromString(const FString& AsString)
{
if (AsString == UE_TABMANAGER_OPENED_TAB_STRING)
{
return ETabState::OpenedTab;
}
else if (AsString == UE_TABMANAGER_CLOSED_TAB_STRING)
{
return ETabState::ClosedTab;
}
else if (AsString == UE_TABMANAGER_INVALID_TAB_STRING)
{
return ETabState::InvalidTab;
}
else if (AsString == UE_TABMANAGER_SIDEBAR_TAB_STRING)
{
return ETabState::SidebarTab;
}
else
{
ensureMsgf(false, TEXT("Invalid tab state."));
return ETabState::OpenedTab;
}
}
//////////////////////////////////////////////////////////////////////////
//
//////////////////////////////////////////////////////////////////////////
FTabManager::FLiveTabSearch::FLiveTabSearch(FName InSearchForTabId)
: SearchForTabId(InSearchForTabId)
{
}
TSharedPtr<SDockTab> FTabManager::FLiveTabSearch::Search(const FTabManager& Manager, FName PlaceholderId, const TSharedRef<SDockTab>& UnmanagedTab) const
{
if ( SearchForTabId != NAME_None )
{
return Manager.FindExistingLiveTab(FTabId(SearchForTabId));
}
else
{
return Manager.FindExistingLiveTab(FTabId(PlaceholderId));
}
}
TSharedPtr<SDockTab> FTabManager::FRequireClosedTab::Search(const FTabManager& Manager, FName PlaceholderId, const TSharedRef<SDockTab>& UnmanagedTab) const
{
return TSharedPtr<SDockTab>();
}
FTabManager::FLastMajorOrNomadTab::FLastMajorOrNomadTab(FName InFallbackTabId)
: FallbackTabId(InFallbackTabId)
{
}
TSharedPtr<SDockTab> FTabManager::FLastMajorOrNomadTab::Search(const FTabManager& Manager, FName PlaceholderId, const TSharedRef<SDockTab>& UnmanagedTab) const
{
TSharedPtr<SDockTab> FoundTab;
if ( UnmanagedTab->GetTabRole() == ETabRole::MajorTab )
{
FoundTab = Manager.FindLastTabInWindow(Manager.LastMajorDockWindow.Pin());
if ( !FoundTab.IsValid() && FallbackTabId != NAME_None )
{
FoundTab = Manager.FindExistingLiveTab(FTabId(FallbackTabId));
}
}
return FoundTab;
}
const TSharedRef<FTabManager::FLayout> FTabManager::FLayout::NullLayout = FTabManager::NewLayout("NullLayout")->AddArea(FTabManager::NewPrimaryArea());
TSharedRef<FTabManager::FLayoutNode> FTabManager::FLayout::NewFromString_Helper( TSharedPtr<FJsonObject> JsonObject )
{
struct local
{
static FTabManager::FArea::EWindowPlacement PlacementFromString( const FString& AsString )
{
static const FString Placement_NoWindow_Str = TEXT("Placement_NoWindow");
static const FString Placement_Automatic_Str = TEXT("Placement_Automatic");
static const FString Placement_Specified_Str = TEXT("Placement_Specified");
if (AsString == Placement_NoWindow_Str)
{
return FTabManager::FArea::Placement_NoWindow;
}
else if (AsString == Placement_Automatic_Str)
{
return FTabManager::FArea::Placement_Automatic;
}
else if (AsString == Placement_Specified_Str)
{
return FTabManager::FArea::Placement_Specified;
}
else
{
ensureMsgf(false, TEXT("Invalid placement mode."));
return FTabManager::FArea::Placement_Automatic;
}
}
static EOrientation OrientationFromString( const FString& AsString )
{
static const FString Orient_Horizontal_Str = TEXT("Orient_Horizontal");
static const FString Orient_Vertical_Str = TEXT("Orient_Vertical");
if (AsString == Orient_Horizontal_Str)
{
return Orient_Horizontal;
}
else if (AsString == Orient_Vertical_Str)
{
return Orient_Vertical;
}
else
{
ensureMsgf(false, TEXT("Invalid orientation."));
return Orient_Horizontal;
}
}
};
const FString NodeType = JsonObject->GetStringField(TEXT("Type"));
if (NodeType == TEXT("Area"))
{
TSharedPtr<FTabManager::FArea> NewArea;
FTabManager::FArea::EWindowPlacement WindowPlacement = local::PlacementFromString( JsonObject->GetStringField(TEXT("WindowPlacement")) );
switch( WindowPlacement )
{
default:
case FTabManager::FArea::Placement_NoWindow:
{
NewArea = FTabManager::NewPrimaryArea();
}
break;
case FTabManager::FArea::Placement_Automatic:
{
FVector2D WindowSize;
WindowSize.X = (float)JsonObject->GetNumberField( TEXT("WindowSize_X") );
WindowSize.Y = (float)JsonObject->GetNumberField( TEXT("WindowSize_Y") );
NewArea = FTabManager::NewArea( WindowSize );
}
break;
case FTabManager::FArea::Placement_Specified:
{
FVector2D WindowPosition = FVector2D::ZeroVector;
FVector2D WindowSize;
WindowPosition.X = (float)JsonObject->GetNumberField( TEXT("WindowPosition_X") );
WindowPosition.Y = (float)JsonObject->GetNumberField( TEXT("WindowPosition_Y") );
WindowSize.X = (float)JsonObject->GetNumberField( TEXT("WindowSize_X") );
WindowSize.Y = (float)JsonObject->GetNumberField( TEXT("WindowSize_Y") );
bool bIsMaximized = JsonObject->GetBoolField(TEXT("bIsMaximized"));
NewArea = FTabManager::NewArea( WindowSize );
NewArea->SetWindow( WindowPosition, bIsMaximized );
}
break;
}
NewArea->SetSizeCoefficient((float)JsonObject->GetNumberField( TEXT("SizeCoefficient") ) );
NewArea->SetOrientation( local::OrientationFromString( JsonObject->GetStringField(TEXT("Orientation")) ) );
TArray< TSharedPtr<FJsonValue> > ChildNodeValues = JsonObject->GetArrayField(TEXT("nodes"));
for( int32 ChildIndex=0; ChildIndex < ChildNodeValues.Num(); ++ChildIndex )
{
NewArea->Split( NewFromString_Helper( ChildNodeValues[ChildIndex]->AsObject() ) );
}
return NewArea.ToSharedRef();
}
else if ( NodeType == TEXT("Splitter") )
{
TSharedRef<FTabManager::FSplitter> NewSplitter = FTabManager::NewSplitter();
NewSplitter->SetSizeCoefficient((float)JsonObject->GetNumberField(TEXT("SizeCoefficient")) );
NewSplitter->SetOrientation( local::OrientationFromString( JsonObject->GetStringField(TEXT("Orientation")) ) );
TArray< TSharedPtr<FJsonValue> > ChildNodeValues = JsonObject->GetArrayField(TEXT("nodes"));
for( int32 ChildIndex=0; ChildIndex < ChildNodeValues.Num(); ++ChildIndex )
{
NewSplitter->Split( NewFromString_Helper( ChildNodeValues[ChildIndex]->AsObject() ) );
}
return NewSplitter;
}
else if ( NodeType == TEXT("Stack") )
{
TSharedRef<FTabManager::FStack> NewStack = FTabManager::NewStack();
NewStack->SetSizeCoefficient((float)JsonObject->GetNumberField(TEXT("SizeCoefficient")) );
NewStack->SetHideTabWell( JsonObject->GetBoolField(TEXT("HideTabWell")) );
if(JsonObject->HasField(TEXT("ForegroundTab")))
{
FName TabId = FName( *JsonObject->GetStringField(TEXT("ForegroundTab")) );
TabId = FGlobalTabmanager::Get()->GetTabTypeForPotentiallyLegacyTab(TabId);
NewStack->SetForegroundTab( FTabId(TabId) );
}
TArray< TSharedPtr<FJsonValue> > TabsAsJson = JsonObject->GetArrayField( TEXT("Tabs") );
for (int32 TabIndex=0; TabIndex < TabsAsJson.Num(); ++TabIndex)
{
TSharedPtr<FJsonObject> TabAsJson = TabsAsJson[TabIndex]->AsObject();
FName TabId = FName( *TabAsJson->GetStringField(TEXT("TabId")) );
TabId = FGlobalTabmanager::Get()->GetTabTypeForPotentiallyLegacyTab(TabId);
FString SidebarLocation;
float SidebarSizeCoefficient = .15f;
bool bPinnedInSidebar = false;
if (TabAsJson->TryGetStringField(TEXT("SidebarLocation"), SidebarLocation))
{
TabAsJson->TryGetNumberField(TEXT("SidebarCoeff"), SidebarSizeCoefficient);
TabAsJson->TryGetBoolField(TEXT("SidebarPinned"), bPinnedInSidebar);
}
NewStack->AddTab(TabId, TabStateFromString( TabAsJson->GetStringField(TEXT("TabState"))), SidebarLocationFromString(SidebarLocation), SidebarSizeCoefficient, bPinnedInSidebar);
}
return NewStack;
}
else
{
ensureMsgf(false, TEXT("Unrecognized node type."));
return FTabManager::NewArea(FTabManager::FallbackWindowSize);
}
}
TSharedPtr<FTabManager::FLayout> FTabManager::FLayout::NewFromString( const FString& LayoutAsText )
{
TSharedPtr<FJsonObject> JsonObject;
TSharedRef< TJsonReader<> > Reader = TJsonReaderFactory<>::Create( LayoutAsText );
if (FJsonSerializer::Deserialize( Reader, JsonObject ))
{
return NewFromJson( JsonObject );
}
return TSharedPtr<FTabManager::FLayout>();
}
TSharedPtr<FTabManager::FLayout> FTabManager::FLayout::NewFromJson( const TSharedPtr<FJsonObject>& LayoutAsJson )
{
if (!LayoutAsJson.IsValid())
{
return TSharedPtr<FTabManager::FLayout>();
}
const FString LayoutName = LayoutAsJson->GetStringField(TEXT("Name"));
TSharedRef<FTabManager::FLayout> NewLayout = FTabManager::NewLayout( *LayoutName );
int32 PrimaryAreaIndex = FMath::TruncToInt((float)LayoutAsJson->GetNumberField(TEXT("PrimaryAreaIndex")) );
TArray< TSharedPtr<FJsonValue> > Areas = LayoutAsJson->GetArrayField(TEXT("Areas"));
for(int32 AreaIndex=0; AreaIndex < Areas.Num(); ++AreaIndex)
{
TSharedRef<FTabManager::FArea> NewArea = StaticCastSharedRef<FTabManager::FArea>( NewFromString_Helper( Areas[AreaIndex]->AsObject() ) );
NewLayout->AddArea( NewArea );
if (AreaIndex == PrimaryAreaIndex)
{
NewLayout->PrimaryArea = NewArea;
}
}
return NewLayout;
}
FName FTabManager::FLayout::GetLayoutName() const
{
return LayoutName;
}
TSharedRef<FJsonObject> FTabManager::FLayout::ToJson() const
{
TSharedRef<FJsonObject> LayoutJson = MakeShareable( new FJsonObject() );
LayoutJson->SetStringField( TEXT("Type"), TEXT("Layout") );
LayoutJson->SetStringField( TEXT("Name"), LayoutName.ToString() );
LayoutJson->SetNumberField( TEXT("PrimaryAreaIndex"), INDEX_NONE );
TArray< TSharedPtr<FJsonValue> > AreasAsJson;
for ( int32 AreaIndex=0; AreaIndex < Areas.Num(); ++AreaIndex )
{
if (PrimaryArea.Pin() == Areas[AreaIndex])
{
LayoutJson->SetNumberField( TEXT("PrimaryAreaIndex"), AreaIndex );
}
AreasAsJson.Add( MakeShareable( new FJsonValueObject( PersistToString_Helper( Areas[AreaIndex] ) ) ) );
}
LayoutJson->SetArrayField( TEXT("Areas"), AreasAsJson );
return LayoutJson;
}
FString FTabManager::FLayout::ToString() const
{
TSharedRef<FJsonObject> LayoutJson = this->ToJson();
FString LayoutAsString;
TSharedRef< TJsonWriter<> > Writer = TJsonWriterFactory<>::Create( &LayoutAsString );
if (!FJsonSerializer::Serialize(LayoutJson, Writer))
{
UE_LOG(LogSlate, Error, TEXT("Failed save layout as Json string: %s"), *GetLayoutName().ToString());
}
return LayoutAsString;
}
TSharedRef<FJsonObject> FTabManager::FLayout::PersistToString_Helper(const TSharedRef<FLayoutNode>& NodeToPersist)
{
TSharedRef<FJsonObject> JsonObj = MakeShareable(new FJsonObject());
TSharedPtr<FTabManager::FStack> NodeAsStack = NodeToPersist->AsStack();
TSharedPtr<FTabManager::FSplitter> NodeAsSplitter = NodeToPersist->AsSplitter();
TSharedPtr<FTabManager::FArea> NodeAsArea = NodeToPersist->AsArea();
JsonObj->SetNumberField( TEXT("SizeCoefficient"), NodeToPersist->SizeCoefficient );
if ( NodeAsArea.IsValid() )
{
JsonObj->SetStringField( TEXT("Type"), TEXT("Area") );
JsonObj->SetStringField( TEXT("Orientation"), (NodeAsArea->GetOrientation() == Orient_Horizontal) ? TEXT("Orient_Horizontal") : TEXT("Orient_Vertical") );
if ( NodeAsArea->WindowPlacement == FArea::Placement_Automatic )
{
JsonObj->SetStringField( TEXT("WindowPlacement"), TEXT("Placement_Automatic") );
JsonObj->SetNumberField( TEXT("WindowSize_X"), NodeAsArea->UnscaledWindowSize.X );
JsonObj->SetNumberField( TEXT("WindowSize_Y"), NodeAsArea->UnscaledWindowSize.Y );
}
else if (NodeAsArea->WindowPlacement == FArea::Placement_NoWindow)
{
JsonObj->SetStringField( TEXT("WindowPlacement"), TEXT("Placement_NoWindow") );
}
else if ( NodeAsArea->WindowPlacement == FArea::Placement_Specified )
{
JsonObj->SetStringField( TEXT("WindowPlacement"), TEXT("Placement_Specified") );
JsonObj->SetNumberField( TEXT("WindowPosition_X"), NodeAsArea->UnscaledWindowPosition.X );
JsonObj->SetNumberField( TEXT("WindowPosition_Y"), NodeAsArea->UnscaledWindowPosition.Y );
JsonObj->SetNumberField( TEXT("WindowSize_X"), NodeAsArea->UnscaledWindowSize.X );
JsonObj->SetNumberField( TEXT("WindowSize_Y"), NodeAsArea->UnscaledWindowSize.Y );
JsonObj->SetBoolField( TEXT("bIsMaximized"), NodeAsArea->bIsMaximized );
}
TArray< TSharedPtr<FJsonValue> > Nodes;
for ( int32 ChildIndex=0; ChildIndex < NodeAsArea->ChildNodes.Num(); ++ChildIndex )
{
Nodes.Add( MakeShareable( new FJsonValueObject( PersistToString_Helper( NodeAsArea->ChildNodes[ChildIndex] ) ) ) );
}
JsonObj->SetArrayField( TEXT("Nodes"), Nodes );
}
else if ( NodeAsSplitter.IsValid() )
{
JsonObj->SetStringField( TEXT("Type"), TEXT("Splitter") );
JsonObj->SetStringField( TEXT("Orientation"), (NodeAsSplitter->GetOrientation() == Orient_Horizontal) ? TEXT("Orient_Horizontal") : TEXT("Orient_Vertical") );
TArray< TSharedPtr<FJsonValue> > Nodes;
for ( int32 ChildIndex=0; ChildIndex < NodeAsSplitter->ChildNodes.Num(); ++ChildIndex )
{
Nodes.Add( MakeShareable( new FJsonValueObject( PersistToString_Helper( NodeAsSplitter->ChildNodes[ChildIndex] ) ) ) );
}
JsonObj->SetArrayField( TEXT("Nodes"), Nodes );
}
else if ( NodeAsStack.IsValid() )
{
JsonObj->SetStringField( TEXT("Type"), TEXT("Stack") );
JsonObj->SetBoolField( TEXT("HideTabWell"), NodeAsStack->bHideTabWell );
if (NodeAsStack->ForegroundTabId.ShouldSaveLayout())
{
JsonObj->SetStringField(TEXT("ForegroundTab"), NodeAsStack->ForegroundTabId.ToString());
}
TArray< TSharedPtr<FJsonValue> > TabsAsJson;
for(const FTab& Tab : NodeAsStack->Tabs)
{
if (Tab.TabId.ShouldSaveLayout())
{
TSharedRef<FJsonObject> TabAsJson = MakeShareable( new FJsonObject() );
TabAsJson->SetStringField( TEXT("TabId"), Tab.TabId.ToString() );
TabAsJson->SetStringField(TEXT("TabState"), StringFromTabState(Tab.TabState));
if (Tab.TabState == ETabState::SidebarTab && Tab.SidebarLocation != ESidebarLocation::None)
{
TabAsJson->SetStringField(TEXT("SidebarLocation"), StringFromSidebarLocation(Tab.SidebarLocation));
TabAsJson->SetNumberField(TEXT("SidebarCoeff"), Tab.SidebarSizeCoefficient);
TabAsJson->SetBoolField(TEXT("SidebarPinned"), Tab.bPinnedInSidebar);
}
TabsAsJson.Add( MakeShareable( new FJsonValueObject(TabAsJson) ) );
}
}
JsonObj->SetArrayField( TEXT("Tabs"), TabsAsJson );
}
else
{
ensureMsgf( false, TEXT("Unable to persist layout node of unknown type.") );
}
return JsonObj;
}
void FTabManager::FLayout::ProcessExtensions(const FLayoutExtender& Extender)
{
// Extend areas first
for (TSharedRef<FTabManager::FArea>& Area : Areas)
{
Extender.ExtendAreaRecursive(Area);
}
struct FTabInformation
{
FTabInformation(FTabManager::FLayout& Layout)
{
for (TSharedRef<FTabManager::FArea>& Area : Layout.Areas)
{
Gather(*Area);
}
}
void Gather(FTabManager::FSplitter& Splitter)
{
for (TSharedRef<FTabManager::FLayoutNode>& Child : Splitter.ChildNodes)
{
TSharedPtr<FTabManager::FStack> Stack = Child->AsStack();
if (Stack.IsValid())
{
StackToParentSplitterMap.Add(Stack.Get(), &Splitter);
AllStacks.Add(Stack.Get());
for (FTabManager::FTab& Tab : Stack->Tabs)
{
AllDefinedTabs.Add(Tab.TabId);
}
continue;
}
TSharedPtr<FTabManager::FSplitter> ChildSplitter = Child->AsSplitter();
if (ChildSplitter.IsValid())
{
Gather(*ChildSplitter);
continue;
}
TSharedPtr<FTabManager::FArea> Area = Child->AsArea();
if (Area.IsValid())
{
Gather(*Area);
continue;
}
}
}
bool Contains(FTabId TabId) const
{
return AllDefinedTabs.Contains(TabId);
}
TMap<FTabManager::FStack*, FTabManager::FSplitter*> StackToParentSplitterMap;
TArray<FTabManager::FStack*> AllStacks;
TSet<FTabId> AllDefinedTabs;
};
FTabInformation AllTabs(*this);
TArray<FTab, TInlineAllocator<1>> ExtendedTabs;
for (FTabManager::FStack* Stack : AllTabs.AllStacks)
{
// First add to the front of the stack
Extender.FindStackExtensions(Stack->GetExtensionId(), ELayoutExtensionPosition::Before, ExtendedTabs);
int32 InsertedTabIndex = 0;
for (FTab& NewTab : ExtendedTabs)
{
if (!AllTabs.Contains(NewTab.TabId))
{
Stack->Tabs.Insert(NewTab, InsertedTabIndex++);
}
}
// This is the per-tab extension section
FSplitter* ParentSplitter = AllTabs.StackToParentSplitterMap.FindRef(Stack);
for (int32 TabIndex = 0; TabIndex < Stack->Tabs.Num();)
{
FTabId TabId = Stack->Tabs[TabIndex].TabId;
Extender.FindTabExtensions(TabId, ELayoutExtensionPosition::Before, ExtendedTabs);
for (FTab& NewTab : ExtendedTabs)
{
if (!AllTabs.Contains(NewTab.TabId))
{
Stack->Tabs.Insert(NewTab, TabIndex++);
}
}
++TabIndex;
Extender.FindTabExtensions(TabId, ELayoutExtensionPosition::After, ExtendedTabs);
for (FTab& NewTab : ExtendedTabs)
{
if (!AllTabs.Contains(NewTab.TabId))
{
Stack->Tabs.Insert(NewTab, TabIndex++);
}
}
if (ParentSplitter)
{
Extender.FindTabExtensions(TabId, ELayoutExtensionPosition::Below, ExtendedTabs);
if (ExtendedTabs.Num())
{
for (FTab& NewTab : ExtendedTabs)
{
if (!AllTabs.Contains(NewTab.TabId))
{
ParentSplitter->InsertAfter(Stack->AsShared(),
FTabManager::NewStack()
->SetHideTabWell(true)
->AddTab(NewTab)
);
}
}
}
Extender.FindTabExtensions(TabId, ELayoutExtensionPosition::Above, ExtendedTabs);
if (ExtendedTabs.Num())
{
for (FTab& NewTab : ExtendedTabs)
{
if (!AllTabs.Contains(NewTab.TabId))
{
ParentSplitter->InsertBefore(Stack->AsShared(),
FTabManager::NewStack()
->SetHideTabWell(true)
->AddTab(NewTab)
);
}
}
}
}
}
// Finally add to the end of the stack
Extender.FindStackExtensions(Stack->GetExtensionId(), ELayoutExtensionPosition::After, ExtendedTabs);
InsertedTabIndex = Stack->Tabs.Num();
for (FTab& NewTab : ExtendedTabs)
{
if (!AllTabs.Contains(NewTab.TabId))
{
Stack->Tabs.Insert(NewTab, InsertedTabIndex++);
}
}
}
}
//////////////////////////////////////////////////////////////////////////
// FTabManager::PrivateApi
//////////////////////////////////////////////////////////////////////////
TSharedPtr<SWindow> FTabManager::FPrivateApi::GetParentWindow() const
{
TSharedPtr<SDockTab> OwnerTab = TabManager.OwnerTabPtr.Pin();
if ( OwnerTab.IsValid() )
{
// The tab was dragged out of some context that is owned by a MajorTab.
// Whichever window possesses the MajorTab should be the parent of the newly created window.
TSharedPtr<SWindow> ParentWindow = FSlateApplication::Get().FindWidgetWindow( OwnerTab.ToSharedRef() );
return ParentWindow;
}
else
{
// This tab is not nested within a major tab, so it is a major tab itself.
// Ask the global tab manager for its root window.
return FGlobalTabmanager::Get()->GetRootWindow();
}
}
void FTabManager::FPrivateApi::OnDockAreaCreated( const TSharedRef<SDockingArea>& NewlyCreatedDockArea )
{
CleanupPointerArray(TabManager.DockAreas);
TabManager.DockAreas.Add( NewlyCreatedDockArea );
}
void FTabManager::FPrivateApi::OnTabRelocated( const TSharedRef<SDockTab>& RelocatedTab, const TSharedPtr<SWindow>& NewOwnerWindow )
{
TabManager.OnTabRelocated(RelocatedTab, NewOwnerWindow);
}
void FTabManager::FPrivateApi::OnTabOpening( const TSharedRef<SDockTab>& TabBeingOpened )
{
TabManager.OnTabOpening(TabBeingOpened);
}
void FTabManager::FPrivateApi::OnTabClosing( const TSharedRef<SDockTab>& TabBeingClosed )
{
TabManager.OnTabClosing(TabBeingClosed);
}
void FTabManager::FPrivateApi::OnDockAreaClosing( const TSharedRef<SDockingArea>& DockAreaThatIsClosing )
{
TSharedPtr<FTabManager::FArea> PersistentDockAreaLayout = StaticCastSharedPtr<FTabManager::FArea>(DockAreaThatIsClosing->GatherPersistentLayout());
if ( PersistentDockAreaLayout.IsValid() )
{
TabManager.CollapsedDockAreas.Add( PersistentDockAreaLayout.ToSharedRef() );
}
}
void FTabManager::FPrivateApi::OnTabManagerClosing()
{
TabManager.OnTabManagerClosing();
}
bool FTabManager::FPrivateApi::CanTabLeaveTabWell(const TSharedRef<const SDockTab>& TabToTest) const
{
return TabManager.bCanDoDragOperation && !(TabToTest->GetLayoutIdentifier() == TabManager.MainNonCloseableTabID);
}
const TArray< TWeakPtr<SDockingArea> >& FTabManager::FPrivateApi::GetLiveDockAreas() const
{
return TabManager.DockAreas;
}
void FTabManager::FPrivateApi::OnTabForegrounded(const TSharedPtr<SDockTab>& NewForegroundTab, const TSharedPtr<SDockTab>& BackgroundedTab)
{
TabManager.OnTabForegrounded(NewForegroundTab, BackgroundedTab);
}
static void SetWindowVisibility(const TArray< TWeakPtr<SDockingArea> >& DockAreas, bool bWindowShouldBeVisible)
{
for (int32 DockAreaIndex = 0; DockAreaIndex < DockAreas.Num(); ++DockAreaIndex)
{
TSharedPtr<SWindow> DockAreaWindow = DockAreas[DockAreaIndex].Pin()->GetParentWindow();
if (DockAreaWindow.IsValid())
{
if (bWindowShouldBeVisible)
{
DockAreaWindow->ShowWindow();
}
else
{
DockAreaWindow->HideWindow();
}
}
}
}
void FTabManager::FPrivateApi::ShowWindows()
{
CleanupPointerArray(TabManager.DockAreas);
SetWindowVisibility(TabManager.DockAreas, true);
}
void FTabManager::FPrivateApi::HideWindows()
{
CleanupPointerArray(TabManager.DockAreas);
SetWindowVisibility(TabManager.DockAreas, false);
}
void FTabManager::FPrivateApi::SetCanDoDeferredLayoutSave(bool bInCanDoDeferredLayoutSave)
{
if (!bInCanDoDeferredLayoutSave)
{
TabManager.ClearPendingLayoutSave();
}
TabManager.bCanDoDeferredLayoutSave = bInCanDoDeferredLayoutSave;
}
FTabManager::FPrivateApi& FTabManager::GetPrivateApi()
{
return *PrivateApi;
}
void FTabManager::SetAllowWindowMenuBar(bool bInAllowWindowMenuBar)
{
bAllowPerWindowMenu = bInAllowWindowMenuBar;
}
void FTabManager::SetMenuMultiBox(const TSharedPtr<FMultiBox> NewMenuMutliBox, const TSharedPtr<SWidget> NewMenuWidget)
{
// We only use the platform native global menu bar on Mac
MenuMultiBox = NewMenuMutliBox;
MenuWidget = NewMenuWidget;
UpdateMainMenu(OwnerTabPtr.Pin(), false);
}
void FTabManager::UpdateMainMenu(TSharedPtr<SDockTab> ForTab, const bool bForce)
{
bool bIsMajorTab = true;
TSharedPtr<SWindow> ParentWindowOfOwningTab;
if (ForTab && (ForTab->GetTabRole() == ETabRole::MajorTab || ForTab->GetVisualTabRole() == ETabRole::MajorTab))
{
ParentWindowOfOwningTab = ForTab->GetParentWindow();
}
else if (auto OwnerTabPinned = OwnerTabPtr.Pin())
{
ParentWindowOfOwningTab = OwnerTabPinned->GetParentWindow();
}
else if (auto MainNonCloseableTabPinned = FindExistingLiveTab(MainNonCloseableTabID))
{
ParentWindowOfOwningTab = MainNonCloseableTabPinned->GetParentWindow();
}
if (bAllowPerWindowMenu)
{
if (ParentWindowOfOwningTab)
{
ParentWindowOfOwningTab->GetTitleBar()->UpdateWindowMenu(MenuWidget);
}
}
else
{
MenuMultiBox.Reset();
MenuWidget.Reset();
if (ParentWindowOfOwningTab)
{
ParentWindowOfOwningTab->GetTitleBar()->UpdateWindowMenu(nullptr);
}
}
}
void FTabManager::SetMainTab(const FTabId& InMainTabID)
{
MainNonCloseableTabID = InMainTabID;
}
void FTabManager::SetMainTab(const TSharedRef<const SDockTab>& InTab)
{
if(!InTab->GetLayoutIdentifier().TabType.IsNone())
{
SetMainTab(InTab->GetLayoutIdentifier());
}
else
{
PendingMainNonClosableTab = InTab;
}
}
void FTabManager::SetReadOnly(bool bInReadOnly)
{
if(bReadOnly != bInReadOnly)
{
bReadOnly = bInReadOnly;
OnReadOnlyModeChanged.Broadcast(bReadOnly);
}
}
bool FTabManager::IsReadOnly()
{
return bReadOnly;
}
bool FTabManager::IsTabCloseable(const TSharedRef<const SDockTab>& InTab) const
{
return MainNonCloseableTabID != InTab->GetLayoutIdentifier();
}
const TSharedRef<FWorkspaceItem> FTabManager::GetLocalWorkspaceMenuRoot() const
{
return LocalWorkspaceMenuRoot.ToSharedRef();
}
TSharedRef<FWorkspaceItem> FTabManager::AddLocalWorkspaceMenuCategory( const FText& CategoryTitle )
{
return LocalWorkspaceMenuRoot->AddGroup( CategoryTitle );
}
void FTabManager::AddLocalWorkspaceMenuItem( const TSharedRef<FWorkspaceItem>& CategoryItem )
{
LocalWorkspaceMenuRoot->AddItem( CategoryItem );
}
void FTabManager::ClearLocalWorkspaceMenuCategories()
{
LocalWorkspaceMenuRoot->ClearItems();
}
TSharedPtr<FTabManager::FStack> FTabManager::FLayoutNode::AsStack()
{
return TSharedPtr<FTabManager::FStack>();
}
TSharedPtr<FTabManager::FSplitter> FTabManager::FLayoutNode::AsSplitter()
{
return TSharedPtr<FTabManager::FSplitter>();
}
TSharedPtr<FTabManager::FArea> FTabManager::FLayoutNode::AsArea()
{
return TSharedPtr<FTabManager::FArea>();
}
void FTabManager::SetOnPersistLayout( const FOnPersistLayout& InHandler )
{
OnPersistLayout_Handler = InHandler;
}
void FTabManager::CloseAllAreas()
{
for ( int32 LiveAreaIndex=0; LiveAreaIndex < DockAreas.Num(); ++LiveAreaIndex )
{
const TSharedPtr<SDockingArea> SomeDockArea = DockAreas[LiveAreaIndex].Pin();
const TSharedPtr<SWindow> ParentWindow = (SomeDockArea.IsValid())
? SomeDockArea->GetParentWindow()
: TSharedPtr<SWindow>();
if (ParentWindow.IsValid())
{
ParentWindow->RequestDestroyWindow();
}
}
DockAreas.Empty();
CollapsedDockAreas.Empty();
InvalidDockAreas.Empty();
}
TSharedRef<FTabManager::FLayout> FTabManager::PersistLayout() const
{
TSharedRef<FLayout> PersistentLayout = FTabManager::NewLayout( this->ActiveLayoutName );
// Persist layout for all LiveAreas
for ( int32 LiveAreaIndex=0; LiveAreaIndex < DockAreas.Num(); ++LiveAreaIndex )
{
TSharedPtr<FArea> PersistedNode;
TSharedPtr<SDockingArea> ChildDockingArea = DockAreas[LiveAreaIndex].Pin();
if ( ChildDockingArea.IsValid() )
{
TSharedPtr<FTabManager::FLayoutNode> LayoutNode = ChildDockingArea->GatherPersistentLayout();
if (LayoutNode.IsValid())
{
PersistedNode = LayoutNode->AsArea();
}
}
if ( PersistedNode.IsValid() )
{
PersistentLayout->AddArea( PersistedNode.ToSharedRef() );
if (PersistedNode->WindowPlacement == FArea::Placement_NoWindow)
{
ensure( !PersistentLayout->PrimaryArea.IsValid() );
PersistentLayout->PrimaryArea = PersistedNode;
}
}
}
// Gather existing persistent layouts for CollapsedAreas
for ( int32 CollapsedAreaIndex=0; CollapsedAreaIndex < CollapsedDockAreas.Num(); ++CollapsedAreaIndex )
{
PersistentLayout->AddArea( CollapsedDockAreas[CollapsedAreaIndex] );
}
// Gather existing persistent layouts for InvalidAreas
for (int32 InvalidAreaIndex = 0; InvalidAreaIndex < InvalidDockAreas.Num(); ++InvalidAreaIndex)
{
PersistentLayout->AddArea(InvalidDockAreas[InvalidAreaIndex]);
}
return PersistentLayout;
}
void FTabManager::SavePersistentLayout()
{
ClearPendingLayoutSave();
const TSharedRef<FLayout> LayoutState = this->PersistLayout();
OnPersistLayout_Handler.ExecuteIfBound(LayoutState);
}
void FTabManager::RequestSavePersistentLayout()
{
// if we already have a request pending, remove it and schedule a new one
// this is to avoid hitches when eg. resizing a docked tab
ClearPendingLayoutSave();
if (!bCanDoDeferredLayoutSave)
{
return;
}
auto OnTick = [ThisWeak = AsWeak()](float FrameTime)
{
if (TSharedPtr<FTabManager> This = ThisWeak.Pin())
{
This->PendingLayoutSaveHandle.Reset();
This->SavePersistentLayout();
}
return false;
};
PendingLayoutSaveHandle = FTSTicker::GetCoreTicker().AddTicker(FTickerDelegate::CreateLambda(OnTick), 5.0f);
}
void FTabManager::ClearPendingLayoutSave()
{
if (PendingLayoutSaveHandle.IsValid())
{
FTSTicker::GetCoreTicker().RemoveTicker(PendingLayoutSaveHandle);
PendingLayoutSaveHandle.Reset();
}
}
FTabSpawnerEntry& FTabManager::RegisterTabSpawner(const FName TabId, const FOnSpawnTab& OnSpawnTab, const FCanSpawnTab& CanSpawnTab)
{
ensure(!TabSpawner.Contains(TabId));
ensure(!FGlobalTabmanager::Get()->IsLegacyTabType(TabId));
LLM_SCOPE_BYTAG(UI_Slate);
TSharedRef<FTabSpawnerEntry> NewSpawnerEntry = MakeShareable(new FTabSpawnerEntry(TabId, OnSpawnTab, CanSpawnTab));
TabSpawner.Add(TabId, NewSpawnerEntry);
return NewSpawnerEntry.Get();
}
bool FTabManager::UnregisterTabSpawner( const FName TabId )
{
return TabSpawner.Remove( TabId ) > 0;
}
void FTabManager::UnregisterAllTabSpawners()
{
TabSpawner.Empty();
}
TSharedPtr<SWidget> FTabManager::RestoreFrom(const TSharedRef<FLayout>& Layout, const TSharedPtr<SWindow>& ParentWindow, const bool bEmbedTitleAreaContent,
const EOutputCanBeNullptr RestoreAreaOutputCanBeNullptr)
{
ActiveLayoutName = Layout->LayoutName;
TSharedPtr<SDockingArea> PrimaryDockArea;
for (int32 AreaIndex=0; AreaIndex < Layout->Areas.Num(); ++AreaIndex )
{
const TSharedRef<FArea>& ThisArea = Layout->Areas[AreaIndex];
// Set all InvalidTab tabs to OpenedTab so the Editor tries to load them. All non-recognized tabs will be set to InvalidTab later.
SetTabsTo(ThisArea, ETabState::OpenedTab, ETabState::InvalidTab);
const bool bIsPrimaryArea = ThisArea->WindowPlacement == FArea::Placement_NoWindow;
const bool bShouldCreate = bIsPrimaryArea || HasValidTabs(ThisArea);
if ( bShouldCreate )
{
TSharedPtr<SDockingArea> RestoredDockArea;
const bool bHasValidOpenTabs = bIsPrimaryArea || HasValidOpenTabs(ThisArea);
if (bHasValidOpenTabs)
{
RestoredDockArea = RestoreArea(ThisArea, ParentWindow, bEmbedTitleAreaContent, RestoreAreaOutputCanBeNullptr);
// Invalidate all tabs in ThisArea because they were not recognized
if (!RestoredDockArea)
{
if (bIsPrimaryArea)
{
UE_LOG(LogSlate, Warning, TEXT("Primary area was not valid for RestoreAreaOutputCanBeNullptr = %d."), int(RestoreAreaOutputCanBeNullptr));
}
SetTabsTo(ThisArea, ETabState::InvalidTab, ETabState::OpenedTab);
InvalidDockAreas.Add(ThisArea);
}
}
else
{
CollapsedDockAreas.Add(ThisArea);
}
if (bIsPrimaryArea && RestoredDockArea.IsValid() && ensure(!PrimaryDockArea.IsValid()))
{
PrimaryDockArea = RestoredDockArea;
}
}
}
// Sanity check
if (RestoreAreaOutputCanBeNullptr == EOutputCanBeNullptr::Never && !PrimaryDockArea.IsValid())
{
UE_LOG(LogSlate, Warning, TEXT("FTabManager::RestoreFrom(): RestoreAreaOutputCanBeNullptr was set to EOutputCanBeNullptr::Never but"
" RestoreFrom() is returning nullptr. I.e., the PrimaryDockArea could not be created. If returning nullptr is possible, set"
" RestoreAreaOutputCanBeNullptr to an option that could return nullptr (e.g., IfNoTabValid, IfNoOpenTabValid). This code might"
" ensure(false) or even check(false) in the future."));
}
UpdateStats();
FinishRestore();
return PrimaryDockArea;
}
struct FPopulateTabSpawnerMenu_Args
{
FPopulateTabSpawnerMenu_Args( const TSharedRef< TArray< TWeakPtr<FTabSpawnerEntry> > >& InAllSpawners, const TSharedRef<FWorkspaceItem>& InMenuNode, int32 InLevel )
: AllSpawners( InAllSpawners )
, MenuNode( InMenuNode )
, Level( InLevel )
{
}
TSharedRef< TArray< TWeakPtr<FTabSpawnerEntry> > > AllSpawners;
TSharedRef<FWorkspaceItem> MenuNode;
int32 Level;
};
/** Scoped guard to ensure that we reset the GuardedValue to false */
struct FScopeGuard
{
FScopeGuard( bool & InGuardedValue )
: GuardedValue(InGuardedValue)
{
GuardedValue = true;
}
~FScopeGuard()
{
GuardedValue = false;
}
private:
bool& GuardedValue;
};
void FTabManager::PopulateTabSpawnerMenu_Helper( FMenuBuilder& PopulateMe, FPopulateTabSpawnerMenu_Args Args )
{
const TArray< TSharedRef<FWorkspaceItem> >& ChildItems = Args.MenuNode->GetChildItems();
bool bFirstItemOnLevel = true;
for ( int32 ChildIndex=0; ChildIndex < ChildItems.Num(); ++ChildIndex )
{
const TSharedRef<FWorkspaceItem>& ChildItem = ChildItems[ChildIndex];
const TSharedPtr<FTabSpawnerEntry> SpawnerNode = ChildItem->AsSpawnerEntry();
if ( SpawnerNode.IsValid() )
{
// LEAF NODE.
// Make a menu item for summoning a tab.
if (Args.AllSpawners->Contains(SpawnerNode.ToSharedRef()))
{
MakeSpawnerMenuEntry(PopulateMe, SpawnerNode);
}
}
else
{
// GROUP NODE
// If it's not empty, create a section and populate it
if ( ChildItem->HasChildrenIn(*Args.AllSpawners) )
{
const FPopulateTabSpawnerMenu_Args Payload( Args.AllSpawners, ChildItem, Args.Level+1 );
if ( Args.Level % 2 == 0 )
{
FString SectionNameStr = ChildItem->GetDisplayName().BuildSourceString();
SectionNameStr.ReplaceInline(TEXT(" "), TEXT(""));
PopulateMe.BeginSection(*SectionNameStr, ChildItem->GetDisplayName());
{
PopulateTabSpawnerMenu_Helper(PopulateMe, Payload);
}
PopulateMe.EndSection();
}
else
{
PopulateMe.AddSubMenu(
ChildItem->GetDisplayName(),
ChildItem->GetTooltipText(),
FNewMenuDelegate::CreateRaw( this, &FTabManager::PopulateTabSpawnerMenu_Helper, Payload ),
false,
ChildItem->GetIcon()
);
}
bFirstItemOnLevel = false;
}
}
}
}
void FTabManager::MakeSpawnerMenuEntry( FMenuBuilder &PopulateMe, const TSharedPtr<FTabSpawnerEntry> &InSpawnerNode )
{
// We don't want to add a menu entry for this tab if it is hidden, or if we are in read only mode and it is asking to be hidden
if (InSpawnerNode->MenuType.Get() != ETabSpawnerMenuType::Hidden && !(bReadOnly && InSpawnerNode->ReadOnlyBehavior == ETabReadOnlyBehavior::Hidden) )
{
PopulateMe.AddMenuEntry(
InSpawnerNode->GetDisplayName().IsEmpty() ? FText::FromName(InSpawnerNode->TabType ) : InSpawnerNode->GetDisplayName(),
InSpawnerNode->GetTooltipText(),
InSpawnerNode->GetIcon(),
GetUIActionForTabSpawnerMenuEntry(InSpawnerNode),
NAME_None,
EUserInterfaceActionType::Check
);
}
}
void FTabManager::PopulateLocalTabSpawnerMenu(FMenuBuilder& PopulateMe)
{
PopulateTabSpawnerMenu(PopulateMe, LocalWorkspaceMenuRoot.ToSharedRef());
}
void FTabManager::PopulateTabSpawnerMenu(FMenuBuilder& PopulateMe, TSharedRef<FWorkspaceItem> MenuStructure)
{
PopulateTabSpawnerMenu(PopulateMe, MenuStructure, true);
}
TArray< TWeakPtr<FTabSpawnerEntry> > FTabManager::CollectSpawners()
{
TArray< TWeakPtr<FTabSpawnerEntry> > AllSpawners;
// Editor-specific tabs
for ( FTabSpawner::TIterator SpawnerIterator(TabSpawner); SpawnerIterator; ++SpawnerIterator )
{
const TSharedRef<FTabSpawnerEntry>& SpawnerEntry = SpawnerIterator.Value();
if ( SpawnerEntry->bAutoGenerateMenuEntry )
{
if (IsAllowedTab(SpawnerEntry->TabType))
{
AllSpawners.AddUnique(SpawnerEntry);
}
}
}
// General Tabs
for ( FTabSpawner::TIterator SpawnerIterator(*NomadTabSpawner); SpawnerIterator; ++SpawnerIterator )
{
const TSharedRef<FTabSpawnerEntry>& SpawnerEntry = SpawnerIterator.Value();
if ( SpawnerEntry->bAutoGenerateMenuEntry )
{
if (IsAllowedTab(SpawnerEntry->TabType))
{
AllSpawners.AddUnique(SpawnerEntry);
}
}
}
return AllSpawners;
}
void FTabManager::PopulateTabSpawnerMenu( FMenuBuilder& PopulateMe, TSharedRef<FWorkspaceItem> MenuStructure, bool bIncludeOrphanedMenus )
{
TSharedRef< TArray< TWeakPtr<FTabSpawnerEntry> > > AllSpawners = MakeShared< TArray< TWeakPtr<FTabSpawnerEntry> > >(CollectSpawners());
if ( bIncludeOrphanedMenus )
{
// Put all orphaned spawners at the top of the menu so programmers go and find them a nice home.
for (const TWeakPtr<FTabSpawnerEntry>& WeakSpawner : *AllSpawners)
{
const TSharedPtr<FTabSpawnerEntry> Spawner = WeakSpawner.Pin();
if (!Spawner)
{
continue;
}
const bool bHasNoPlaceInMenuStructure = !Spawner->GetParent().IsValid();
if ( bHasNoPlaceInMenuStructure )
{
this->MakeSpawnerMenuEntry(PopulateMe, Spawner);
}
}
}
PopulateTabSpawnerMenu_Helper( PopulateMe, FPopulateTabSpawnerMenu_Args( AllSpawners, MenuStructure, 0 ) );
}
void FTabManager::PopulateTabSpawnerMenu( FMenuBuilder &PopulateMe, const FName& TabType )
{
TSharedPtr<FTabSpawnerEntry> Spawner = FindTabSpawnerFor(TabType);
if (Spawner.IsValid())
{
MakeSpawnerMenuEntry(PopulateMe, Spawner);
}
else
{
UE_LOG(LogSlate, Warning, TEXT("PopulateTabSpawnerMenu failed to find entry for %s"), *(TabType.ToString()));
}
}
void FTabManager::DrawAttention( const TSharedRef<SDockTab>& TabToHighlight )
{
// Bring the tab to front.
const TSharedPtr<SDockingArea> DockingArea = TabToHighlight->GetDockArea();
if (DockingArea.IsValid())
{
const TSharedRef<FTabManager> ManagerOfTabToHighlight = DockingArea->GetTabManager();
if (ManagerOfTabToHighlight != FGlobalTabmanager::Get())
{
FGlobalTabmanager::Get()->DrawAttentionToTabManager(ManagerOfTabToHighlight);
}
TSharedPtr<SWindow> OwnerWindow = DockingArea->GetParentWindow();
if (SWindow* OwnerWindowPtr = OwnerWindow.Get())
{
// When should we force a window to the front?
// 1) The owner window is already active, so we know the user is using this screen.
// 2) This window is a child window of another already active window (same as 1).
// 3) Slate is currently processing input, which would imply we got this request at the behest of a user's click or press.
if (OwnerWindowPtr->IsActive() || OwnerWindowPtr->HasActiveParent() || FSlateApplication::Get().IsProcessingInput())
{
OwnerWindowPtr->BringToFront();
}
}
if (!DockingArea->TryOpenSidebarDrawer(TabToHighlight))
{
TabToHighlight->GetParentDockTabStack()->BringToFront(TabToHighlight);
}
TabToHighlight->FlashTab();
FGlobalTabmanager::Get()->UpdateMainMenu(TabToHighlight, true);
}
}
void FTabManager::InsertNewDocumentTab(FName PlaceholderId, FName NewTabId, const FSearchPreference& SearchPreference, const TSharedRef<SDockTab>& UnmanagedTab)
{
InsertDocumentTab(PlaceholderId, NewTabId, SearchPreference, UnmanagedTab, true);
}
void FTabManager::InsertNewDocumentTab(FName PlaceholderId, const FSearchPreference& SearchPreference, const TSharedRef<SDockTab>& UnmanagedTab)
{
InsertDocumentTab(PlaceholderId, PlaceholderId, SearchPreference, UnmanagedTab, true);
}
void FTabManager::InsertNewDocumentTab( FName PlaceholderId, ESearchPreference::Type SearchPreference, const TSharedRef<SDockTab>& UnmanagedTab )
{
switch (SearchPreference)
{
case ESearchPreference::PreferLiveTab:
{
FLiveTabSearch Search;
InsertDocumentTab(PlaceholderId, PlaceholderId, Search, UnmanagedTab, true);
break;
}
case ESearchPreference::RequireClosedTab:
{
FRequireClosedTab Search;
InsertDocumentTab(PlaceholderId, PlaceholderId, Search, UnmanagedTab, true);
break;
}
default:
check(false);
break;
}
}
void FTabManager::RestoreDocumentTab( FName PlaceholderId, ESearchPreference::Type SearchPreference, const TSharedRef<SDockTab>& UnmanagedTab )
{
switch (SearchPreference)
{
case ESearchPreference::PreferLiveTab:
{
FLiveTabSearch Search;
InsertDocumentTab(PlaceholderId, PlaceholderId, Search, UnmanagedTab, false);
break;
}
case ESearchPreference::RequireClosedTab:
{
FRequireClosedTab Search;
InsertDocumentTab(PlaceholderId, PlaceholderId, Search, UnmanagedTab, false);
break;
}
default:
check(false);
break;
}
}
TSharedPtr<SDockTab> FTabManager::TryInvokeTab(const FTabId& TabId, bool bInvokeAsInactive)
{
TSharedPtr<SDockTab> NewTab = InvokeTab_Internal(TabId, bInvokeAsInactive, true);
if (!NewTab.IsValid())
{
return NewTab;
}
TSharedPtr<SWindow> ParentWindowPtr = NewTab->GetParentWindow();
if ((NewTab->GetTabRole() == ETabRole::MajorTab || NewTab->GetTabRole() == ETabRole::NomadTab) && ParentWindowPtr.IsValid() && ParentWindowPtr != FGlobalTabmanager::Get()->GetRootWindow())
{
ParentWindowPtr->SetTitle(NewTab->GetTabLabel());
}
#if PLATFORM_MAC
FPlatformApplicationMisc::bChachedMacMenuStateNeedsUpdate = true;
#endif
return NewTab;
}
TSharedPtr<SDockTab> FTabManager::InvokeTab_Internal(const FTabId& TabId, bool bInvokeAsInactive, bool bForceOpenWindowIfNeeded)
{
// Tab Spawning Rules:
//
// * Find live instance --yes--> use it.
// |no
// v
// * [non-Document only]
// Find closed instance with matching TabId --yes--> restore it.
// |no
// v
// * Find any tab of matching TabType (closed or open) --yes--> spawn next to it.
// | no
// v
// * Is a nomad tab and we are NOT the global tab manager --yes--> try to invoke in the global tab manager
// | no
// v
// * Spawn in a new window.
if (!IsAllowedTab(TabId))
{
UE_LOG(LogTabManager, Warning, TEXT("Cannot spawn tab for '%s'"), *(TabId.ToString()));
return nullptr;
}
TSharedPtr<FTabSpawnerEntry> Spawner = FindTabSpawnerFor(TabId.TabType);
if ( !Spawner.IsValid() )
{
UE_LOG(LogTabManager, Warning, TEXT("Cannot spawn tab because no spawner is registered for '%s'"), *(TabId.ToString()));
}
else
{
TSharedPtr<SDockTab> ExistingTab = Spawner->OnFindTabToReuse.IsBound()
? Spawner->OnFindTabToReuse.Execute( TabId )
: Spawner->SpawnedTabPtr.Pin();
if (ExistingTab.IsValid())
{
TSharedPtr<SDockTab> MajorTab;
if (TSharedPtr<FTabManager> ExistingTabManager = ExistingTab->GetTabManagerPtr())
{
MajorTab = FGlobalTabmanager::Get()->GetMajorTabForTabManager(ExistingTabManager.ToSharedRef());
}
// Rules for drawing attention to a tab:
// 1. Tab is not active
// 2. Tab's owning major tab is not in the foreground (making the tab we want to draw attention to is not visible)
// 3. Tab is nomad and is not in the foreground
// If the tab is not active or the tabs major tab is not in the foreground, activate it
if (!bInvokeAsInactive && (!ExistingTab->IsActive() || (MajorTab && !MajorTab->IsForeground()) || !ExistingTab->IsForeground()))
{
// Draw attention to this tab if it didn't already have focus
DrawAttention(ExistingTab.ToSharedRef());
}
return ExistingTab.ToSharedRef();
}
}
// Tab is not live. Figure out where to spawn it.
TSharedPtr<SDockingTabStack> StackToSpawnIn = bForceOpenWindowIfNeeded ? AttemptToOpenTab( TabId, true ) : FindPotentiallyClosedTab( TabId );
if (StackToSpawnIn.IsValid())
{
const TSharedPtr<SDockTab> NewTab = SpawnTab(TabId, GetPrivateApi().GetParentWindow());
if (NewTab.IsValid())
{
StackToSpawnIn->OpenTab(NewTab.ToSharedRef(), INDEX_NONE, bInvokeAsInactive);
NewTab->PlaySpawnAnim();
FGlobalTabmanager::Get()->UpdateMainMenu(NewTab.ToSharedRef(), false);
}
return NewTab;
}
else if ( FGlobalTabmanager::Get() != SharedThis(this) && NomadTabSpawner->Contains(TabId.TabType) )
{
// This tab could have been spawned in the global tab manager since it has a nomad tab spawner
return FGlobalTabmanager::Get()->InvokeTab_Internal(TabId, bInvokeAsInactive, bForceOpenWindowIfNeeded);
}
else
{
const TSharedRef<FArea> NewAreaForTab = GetAreaForTabId(TabId);
NewAreaForTab
->Split
(
FTabManager::NewStack()
->AddTab( TabId, ETabState::OpenedTab )
);
TSharedPtr<SDockingArea> DockingArea = RestoreArea(NewAreaForTab, GetPrivateApi().GetParentWindow());
if (DockingArea && DockingArea->GetAllChildTabs().Num() > 0)
{
const TSharedPtr<SDockTab> NewlyOpenedTab = DockingArea->GetAllChildTabs()[0];
check(NewlyOpenedTab.IsValid());
return NewlyOpenedTab.ToSharedRef();
}
else
{
return nullptr;
}
}
}
TSharedPtr<SDockingTabStack> FTabManager::FindPotentiallyClosedTab( const FTabId& ClosedTabId )
{
return AttemptToOpenTab( ClosedTabId );
}
TSharedPtr<SDockingTabStack> FTabManager::AttemptToOpenTab( const FTabId& ClosedTabId, bool bForceOpenWindowIfNeeded )
{
TSharedPtr<SDockingTabStack> StackWithClosedTab;
FTabMatcher TabMatcher( ClosedTabId );
// Search among the COLLAPSED AREAS
const int32 CollapsedAreaWithMatchingTabIndex = FindTabInCollapsedAreas( TabMatcher );
if ( CollapsedAreaWithMatchingTabIndex != INDEX_NONE )
{
TSharedRef<FTabManager::FArea> CollapsedAreaWithMatchingTab = CollapsedDockAreas[CollapsedAreaWithMatchingTabIndex];
// If this is not the global tab manager and the tab is a NomadTab in a floating window, then remove it from the collapsed area.
if (FGlobalTabmanager::Get() != SharedThis(this) &&
NomadTabSpawner->Contains(ClosedTabId.TabType) &&
CollapsedAreaWithMatchingTab->WindowPlacement != FTabManager::FArea::EWindowPlacement::Placement_NoWindow)
{
RemoveTabFromCollapsedAreas(TabMatcher);
}
else
{
TSharedPtr<SDockingArea> RestoredArea = RestoreArea(CollapsedDockAreas[CollapsedAreaWithMatchingTabIndex],
GetPrivateApi().GetParentWindow(), false, EOutputCanBeNullptr::Never, bForceOpenWindowIfNeeded);
check(RestoredArea.IsValid());
// We have just un-collapsed this dock area.
// Don't rely on the collapsed tab index: RestoreArea() can end up kicking the task graph which could do other tab work and modify the CollapsedDockAreas array.
CollapsedDockAreas.Remove(CollapsedAreaWithMatchingTab);
if (RestoredArea.IsValid())
{
StackWithClosedTab = FindTabInLiveArea(TabMatcher, StaticCastSharedRef<SDockingArea>(RestoredArea->AsShared()));
}
}
}
if ( !StackWithClosedTab.IsValid() )
{
// Search among the LIVE AREAS
StackWithClosedTab = FindTabInLiveAreas( TabMatcher );
}
return StackWithClosedTab;
}
FUIAction FTabManager::GetUIActionForTabSpawnerMenuEntry(TSharedPtr<FTabSpawnerEntry> InTabMenuEntry)
{
auto CanExecuteMenuEntry = [](TWeakPtr<FTabSpawnerEntry> SpawnerNode) -> bool
{
TSharedPtr<FTabSpawnerEntry> SpawnerNodePinned = SpawnerNode.Pin();
if (SpawnerNodePinned.IsValid() && SpawnerNodePinned->MenuType.Get() == ETabSpawnerMenuType::Enabled)
{
return SpawnerNodePinned->CanSpawnTab.IsBound() ? SpawnerNodePinned->CanSpawnTab.Execute(FSpawnTabArgs(TSharedPtr<SWindow>(), SpawnerNodePinned->TabType)) : true;
}
return false;
};
return FUIAction(
FExecuteAction::CreateSP(SharedThis(this), &FTabManager::InvokeTabForMenu, InTabMenuEntry->TabType),
FCanExecuteAction::CreateStatic(CanExecuteMenuEntry, TWeakPtr<FTabSpawnerEntry>(InTabMenuEntry)),
FIsActionChecked::CreateSP(InTabMenuEntry.ToSharedRef(), &FTabSpawnerEntry::IsSoleTabInstanceSpawned)
);
}
void FTabManager::InvokeTabForMenu( FName TabId )
{
TryInvokeTab(TabId);
}
void FTabManager::InsertDocumentTab(FName PlaceholderId, const FSearchPreference& SearchPreference, const TSharedRef<SDockTab>& UnmanagedTab, bool bPlaySpawnAnim)
{
InsertDocumentTab(PlaceholderId, PlaceholderId, SearchPreference, UnmanagedTab, bPlaySpawnAnim);
}
void FTabManager::InsertDocumentTab(FName PlaceholderId, FName NewTabId, const FSearchPreference& SearchPreference, const TSharedRef<SDockTab>& UnmanagedTab, bool bPlaySpawnAnim)
{
bool bWasUnmanagedTabOpened = true;
const bool bTabNotManaged = ensure( ! FindTabInLiveAreas( FTabMatcher(UnmanagedTab->GetLayoutIdentifier()) ).IsValid() );
UnmanagedTab->SetLayoutIdentifier( FTabId(NewTabId, LastDocumentUID++) );
if (bTabNotManaged)
{
OpenUnmanagedTab(PlaceholderId, SearchPreference, UnmanagedTab);
}
DrawAttention(UnmanagedTab);
if (bPlaySpawnAnim)
{
UnmanagedTab->PlaySpawnAnim();
}
}
void FTabManager::OpenUnmanagedTab(FName PlaceholderId, const FSearchPreference& SearchPreference, const TSharedRef<SDockTab>& UnmanagedTab)
{
TSharedPtr<SDockTab> LiveTab = SearchPreference.Search(*this, PlaceholderId, UnmanagedTab);
if (LiveTab.IsValid())
{
LiveTab->GetParent()->GetParentDockTabStack()->OpenTab( UnmanagedTab );
}
else
{
TSharedPtr<SDockingTabStack> StackToSpawnIn = AttemptToOpenTab( PlaceholderId, true );
if (StackToSpawnIn.IsValid())
{
StackToSpawnIn->OpenTab(UnmanagedTab);
}
else
{
UE_LOG(LogTabManager, Warning, TEXT("Unable to insert tab '%s'."), *(PlaceholderId.ToString()));
LiveTab = InvokeTab_Internal( FTabId( PlaceholderId ) );
if (LiveTab.IsValid())
{
LiveTab->GetParent()->GetParentDockTabStack()->OpenTab( UnmanagedTab );
}
}
}
}
FTabManager::FTabManager( const TSharedPtr<SDockTab>& InOwnerTab, const TSharedRef<FTabSpawner> & InNomadTabSpawner )
: NomadTabSpawner(InNomadTabSpawner)
, OwnerTabPtr( InOwnerTab )
, PrivateApi( MakeShareable(new FPrivateApi(*this)) )
, LastDocumentUID( 0 )
, TabPermissionList( MakeShareable(new FNamePermissionList()) )
{
LocalWorkspaceMenuRoot = FWorkspaceItem::NewGroup(LOCTEXT("LocalWorkspaceRoot", "Local Workspace Root"));
}
TSharedPtr<SDockingArea> FTabManager::RestoreArea(const TSharedRef<FArea>& AreaToRestore, const TSharedPtr<SWindow>& InParentWindow, const bool bEmbedTitleAreaContent, const EOutputCanBeNullptr OutputCanBeNullptr, bool bForceOpenWindowIfNeeded)
{
// Sidebar tabs for this area
FSidebarTabLists SidebarTabs;
TemporarilySidebaredTabs.Empty();
if (TSharedPtr<SDockingNode> RestoredNode = RestoreArea_Helper(AreaToRestore, InParentWindow, bEmbedTitleAreaContent, SidebarTabs, OutputCanBeNullptr, bForceOpenWindowIfNeeded))
{
TSharedRef<SDockingArea> RestoredArea = StaticCastSharedRef<SDockingArea>(RestoredNode->AsShared());
RestoredArea->CleanUp(SDockingNode::TabRemoval_None);
RestoredArea->AddSidebarTabsFromRestoredLayout(SidebarTabs);
for (const TSharedRef<SDockTab>& Tab : SidebarTabs.LeftSidebarTabs)
{
TemporarilySidebaredTabs.Add(Tab);
}
for (const TSharedRef<SDockTab>& Tab : SidebarTabs.RightSidebarTabs)
{
TemporarilySidebaredTabs.Add(Tab);
}
return RestoredArea;
}
else
{
check(OutputCanBeNullptr != EOutputCanBeNullptr::Never);
return nullptr;
}
}
TSharedPtr<SDockingNode> FTabManager::RestoreArea_Helper(const TSharedRef<FLayoutNode>& LayoutNode, const TSharedPtr<SWindow>& ParentWindow, const bool bEmbedTitleAreaContent,
FSidebarTabLists& OutSidebarTabs, const EOutputCanBeNullptr OutputCanBeNullptr, bool bForceOpenWindowIfNeeded)
{
#if WITH_EDITOR
FSlateApplication::FScopedPreventDebuggingMode Scope(LOCTEXT("RestoringTabsDebugScope", "Disabling debug due to being in tab restore, breakpoints in constructors can infinitely stall during restore."));
#endif
TSharedPtr<FTabManager::FStack> NodeAsStack = LayoutNode->AsStack();
TSharedPtr<FTabManager::FSplitter> NodeAsSplitter = LayoutNode->AsSplitter();
TSharedPtr<FTabManager::FArea> NodeAsArea = LayoutNode->AsArea();
const bool bCanOutputBeNullptr = (OutputCanBeNullptr != EOutputCanBeNullptr::Never);
if (NodeAsStack.IsValid())
{
TSharedPtr<SDockTab> WidgetToActivate;
TSharedPtr<SDockingTabStack> NewStackWidget;
// Should we init NewStackWidget before the for loop? It depends on OutputCanBeNullptr
bool bIsNewStackWidgetInit = false;
// 1. If EOutputCanBeNullptr::Never, function cannot return nullptr
if (OutputCanBeNullptr == EOutputCanBeNullptr::Never)
{
bIsNewStackWidgetInit = true;
}
// 2. If EOutputCanBeNullptr::IfNoTabValid, we must init the SWidget as soon as any tab is valid for spawning
else if (OutputCanBeNullptr == EOutputCanBeNullptr::IfNoTabValid)
{
// Note: IsValidTabForSpawning does not check whether SpawnTab() will return nullptr
for (const FTab& SomeTab : NodeAsStack->Tabs)
{
if (IsValidTabForSpawning(SomeTab))
{
bIsNewStackWidgetInit = true;
break;
}
}
}
// 3. If EOutputCanBeNullptr::IfNoOpenTabValid, we must init the SWidget as soon as any open tab is valid for spawning. For efficiency, done in the for loop
// 4. Else, case not handled --> error
else if (OutputCanBeNullptr != EOutputCanBeNullptr::IfNoOpenTabValid)
{
check(false);
}
// Initialize the SWidget already?
if (bIsNewStackWidgetInit)
{
NewStackWidget = SNew(SDockingTabStack, NodeAsStack.ToSharedRef());
NewStackWidget->SetSizeCoefficient(LayoutNode->GetSizeCoefficient());
}
// Open Tabs
for (const FTab& SomeTab : NodeAsStack->Tabs)
{
if ((SomeTab.TabState == ETabState::OpenedTab || SomeTab.TabState == ETabState::SidebarTab) && IsValidTabForSpawning(SomeTab))
{
const bool bCanUnrecognizedTabBeNullptr = true;
const TSharedPtr<SDockTab> NewTabWidget = SpawnTab(SomeTab.TabId, ParentWindow, bCanUnrecognizedTabBeNullptr);
if (NewTabWidget.IsValid())
{
if (SomeTab.TabId == NodeAsStack->ForegroundTabId)
{
ensure(SomeTab.TabState == ETabState::OpenedTab);
WidgetToActivate = NewTabWidget;
}
// First time initialization: Only if at least a valid NewTabWidget
if (!NewStackWidget)
{
NewStackWidget = SNew(SDockingTabStack, NodeAsStack.ToSharedRef());
NewStackWidget->SetSizeCoefficient(LayoutNode->GetSizeCoefficient());
}
if (SomeTab.TabState == ETabState::OpenedTab)
{
NewStackWidget->AddTabWidget(NewTabWidget.ToSharedRef());
}
else
{
// Let the stack know we have a tab that belongs in its stack that is currently in a sidebar
NewStackWidget->AddSidebarTab(NewTabWidget.ToSharedRef());
if (SomeTab.SidebarLocation == ESidebarLocation::Left)
{
OutSidebarTabs.LeftSidebarTabs.Add(NewTabWidget.ToSharedRef());
}
else
{
ensure(SomeTab.SidebarLocation == ESidebarLocation::Right);
OutSidebarTabs.RightSidebarTabs.Add(NewTabWidget.ToSharedRef());
}
}
}
}
}
if(WidgetToActivate.IsValid())
{
WidgetToActivate->ActivateInParent(ETabActivationCause::SetDirectly);
if ((WidgetToActivate->GetTabRole() == ETabRole::MajorTab || WidgetToActivate->GetTabRole() == ETabRole::NomadTab)
&& ParentWindow.IsValid() && ParentWindow != FGlobalTabmanager::Get()->GetRootWindow())
{
ParentWindow->SetTitle(WidgetToActivate->GetTabLabel());
}
}
return NewStackWidget;
}
else if ( NodeAsArea.IsValid() )
{
const bool bSplitterIsDockArea = NodeAsArea.IsValid();
const bool bDockNeedsNewWindow = NodeAsArea.IsValid() && (NodeAsArea->WindowPlacement != FArea::Placement_NoWindow);
TSharedPtr<SDockingArea> NewDockAreaWidget;
if ( bDockNeedsNewWindow )
{
// The layout node we are restoring is a dock area.
// It needs a new window into which it will land.
const bool bIsChildWindow = ParentWindow.IsValid();
const bool bAutoPlacement = (NodeAsArea->WindowPlacement == FArea::Placement_Automatic);
TSharedRef<SWindow> NewWindow = (bAutoPlacement)
? SNew(SWindow)
.AutoCenter( EAutoCenter::PreferredWorkArea )
.ClientSize( NodeAsArea->UnscaledWindowSize )
.CreateTitleBar( false )
.IsInitiallyMaximized( NodeAsArea->bIsMaximized )
: SNew(SWindow)
.AutoCenter( EAutoCenter::None )
.ScreenPosition( NodeAsArea->UnscaledWindowPosition )
.ClientSize( NodeAsArea->UnscaledWindowSize )
.CreateTitleBar( false )
.IsInitiallyMaximized( NodeAsArea->bIsMaximized );
// Set a default title; restoring the splitter content may override this if it activates a tab
NewWindow->SetTitle(FGlobalTabmanager::Get()->GetApplicationTitle());
// We need to add the new window now before we recursively restore any content.
// The reason for this is that NewWindow may become the ParentWindow for another window as we restore content.
// We destroy the new window as we unwind if it ends up being extraneous
if (bIsChildWindow)
{
FSlateApplication::Get().AddWindowAsNativeChild(NewWindow, ParentWindow.ToSharedRef());
}
else
{
FSlateApplication::Get().AddWindow(NewWindow);
}
TArray<TSharedRef<SDockingNode>> DockingNodes;
if (CanRestoreSplitterContent(DockingNodes, NodeAsArea.ToSharedRef(), NewWindow, OutSidebarTabs, OutputCanBeNullptr))
{
NewWindow->SetContent(SAssignNew(NewDockAreaWidget, SDockingArea, SharedThis(this), NodeAsArea.ToSharedRef()).ParentWindow(NewWindow));
// Restore content
if (!bCanOutputBeNullptr)
{
RestoreSplitterContent(NodeAsArea.ToSharedRef(), NewDockAreaWidget.ToSharedRef(), NewWindow, OutSidebarTabs);
}
else
{
RestoreSplitterContent(DockingNodes, NewDockAreaWidget.ToSharedRef());
}
if (bIsChildWindow)
{
// Recursively check to see how many actually spawned tabs there are in this dock area.
const int32 TotalNumTabs = NewDockAreaWidget->GetNumTabs();
// If there are none and we aren't requested to force open the window, then destroy the window.
if (TotalNumTabs == 0 && !bForceOpenWindowIfNeeded)
{
NewWindow->RequestDestroyWindow();
}
}
}
else
{
NewWindow->RequestDestroyWindow();
}
}
else
{
TArray<TSharedRef<SDockingNode>> DockingNodes;
if (CanRestoreSplitterContent(DockingNodes, NodeAsArea.ToSharedRef(), ParentWindow, OutSidebarTabs, OutputCanBeNullptr))
{
SAssignNew(NewDockAreaWidget, SDockingArea, SharedThis(this), NodeAsArea.ToSharedRef())
// We only want to set a parent window on this dock area, if we need to have title area content
// embedded within it. SDockingArea assumes that if it has a parent window set, then it needs to have
// title area content
.ParentWindow(bEmbedTitleAreaContent ? ParentWindow : TSharedPtr<SWindow>())
// Never manage these windows, even if a parent window is set. The owner will take care of
// destroying these windows.
.ShouldManageParentWindow(false);
// Restore content
if (!bCanOutputBeNullptr)
{
RestoreSplitterContent(NodeAsArea.ToSharedRef(), NewDockAreaWidget.ToSharedRef(), ParentWindow, OutSidebarTabs);
}
else
{
RestoreSplitterContent(DockingNodes, NewDockAreaWidget.ToSharedRef());
}
}
}
return NewDockAreaWidget;
}
else if ( NodeAsSplitter.IsValid() )
{
TArray<TSharedRef<SDockingNode>> DockingNodes;
if (CanRestoreSplitterContent(DockingNodes, NodeAsSplitter.ToSharedRef(), ParentWindow, OutSidebarTabs, OutputCanBeNullptr))
{
TSharedRef<SDockingSplitter> NewSplitterWidget = SNew( SDockingSplitter, NodeAsSplitter.ToSharedRef() );
NewSplitterWidget->SetSizeCoefficient(LayoutNode->GetSizeCoefficient());
// Restore content
if (!bCanOutputBeNullptr)
{
RestoreSplitterContent(NodeAsSplitter.ToSharedRef(), NewSplitterWidget, ParentWindow, OutSidebarTabs);
}
else
{
RestoreSplitterContent(DockingNodes, NewSplitterWidget);
}
return NewSplitterWidget;
}
else
{
return nullptr;
}
}
else
{
ensureMsgf( false, TEXT("Unexpected node type") );
TSharedRef<SDockingTabStack> NewStackWidget = SNew(SDockingTabStack, FTabManager::NewStack());
NewStackWidget->OpenTab(SpawnTab(FName(NAME_None), ParentWindow, bCanOutputBeNullptr).ToSharedRef());
return NewStackWidget;
}
}
bool FTabManager::CanRestoreSplitterContent(TArray<TSharedRef<SDockingNode>>& DockingNodes, const TSharedRef<FSplitter>& SplitterNode, const TSharedPtr<SWindow>& ParentWindow, FSidebarTabLists& OutSidebarTabs, const EOutputCanBeNullptr OutputCanBeNullptr)
{
if (OutputCanBeNullptr == EOutputCanBeNullptr::Never)
{
return true;
}
DockingNodes.Empty();
// Restore the contents of this splitter.
for ( int32 ChildNodeIndex = 0; ChildNodeIndex < SplitterNode->ChildNodes.Num(); ++ChildNodeIndex )
{
const TSharedRef<FLayoutNode> ThisChildNode = SplitterNode->ChildNodes[ChildNodeIndex];
const bool bEmbedTitleAreaContent = false;
const TSharedPtr<SDockingNode> ThisChildNodeWidget = RestoreArea_Helper(ThisChildNode, ParentWindow, bEmbedTitleAreaContent, OutSidebarTabs, OutputCanBeNullptr);
if (ThisChildNodeWidget)
{
const TSharedRef<SDockingNode> ThisChildNodeWidgetRef = StaticCastSharedRef<SDockingNode>(ThisChildNodeWidget->AsShared());
DockingNodes.Add(ThisChildNodeWidgetRef);
}
}
return (DockingNodes.Num() > 0);
}
void FTabManager::RestoreSplitterContent( const TArray<TSharedRef<SDockingNode>>& DockingNodes, const TSharedRef<SDockingSplitter>& SplitterWidget)
{
for (const TSharedRef<SDockingNode>& DockingNode : DockingNodes)
{
SplitterWidget->AddChildNode(DockingNode, INDEX_NONE);
}
}
void FTabManager::RestoreSplitterContent(const TSharedRef<FSplitter>& SplitterNode, const TSharedRef<SDockingSplitter>& SplitterWidget, const TSharedPtr<SWindow>& ParentWindow, FSidebarTabLists& OutSidebarTabs)
{
// Restore the contents of this splitter.
for ( int32 ChildNodeIndex = 0; ChildNodeIndex < SplitterNode->ChildNodes.Num(); ++ChildNodeIndex )
{
TSharedRef<FLayoutNode> ThisChildNode = SplitterNode->ChildNodes[ChildNodeIndex];
const bool bEmbedTitleAreaContent = false;
TSharedPtr<SDockingNode> ThisChildNodeWidget = RestoreArea_Helper(ThisChildNode, ParentWindow, bEmbedTitleAreaContent, OutSidebarTabs);
check(ThisChildNodeWidget.IsValid());
if (ThisChildNodeWidget)
{
const TSharedRef<SDockingNode> ThisChildNodeWidgetRef = StaticCastSharedRef<SDockingNode>(ThisChildNodeWidget->AsShared());
SplitterWidget->AddChildNode( ThisChildNodeWidgetRef, INDEX_NONE );
}
}
}
bool FTabManager::HasTabSpawner(FName TabId) const
{
// Look for a spawner in this tab manager.
const TSharedRef<FTabSpawnerEntry>* Spawner = TabSpawner.Find(TabId);
if (Spawner == nullptr)
{
Spawner = NomadTabSpawner->Find(TabId);
}
return Spawner != nullptr;
}
TSharedRef<FNamePermissionList>& FTabManager::GetTabPermissionList()
{
return TabPermissionList;
}
bool FTabManager::IsValidTabForSpawning( const FTab& SomeTab ) const
{
if (!IsAllowedTab(SomeTab.TabId))
{
return false;
}
// Nomad tabs being restored from layouts should not be spawned if the nomad tab is already spawned.
TSharedRef<FTabSpawnerEntry>* NomadSpawner = NomadTabSpawner->Find( SomeTab.TabId.TabType );
return ( !NomadSpawner || !NomadSpawner->Get().IsSoleTabInstanceSpawned() || NomadSpawner->Get().OnFindTabToReuse.IsBound() );
}
bool FTabManager::IsAllowedTab(const FTabId& TabId) const
{
bool bAllowed = true;
// If we are in read-only mode, make sure this tab doesn't want to be hidden
if(bReadOnly)
{
TOptional<ETabReadOnlyBehavior> TabReadOnlyBehavior = GetTabReadOnlyBehavior(TabId);
if(TabReadOnlyBehavior.IsSet())
{
bAllowed &= (TabReadOnlyBehavior.GetValue() != ETabReadOnlyBehavior::Hidden);
}
}
bAllowed &= IsAllowedTabType(TabId.TabType);
return bAllowed;
}
TOptional<ETabReadOnlyBehavior> FTabManager::GetTabReadOnlyBehavior(const FTabId& TabId) const
{
if (const TSharedPtr<const FTabSpawnerEntry> Spawner = FindTabSpawnerFor(TabId.TabType))
{
return Spawner->ReadOnlyBehavior;
}
return TOptional<ETabReadOnlyBehavior>();
}
bool FTabManager::IsAllowedTabType(const FName TabType) const
{
const bool bIsAllowed = TabType == NAME_None || TabPermissionList->PassesFilter(TabType);
if (!bIsAllowed)
{
UE_LOG(LogSlate, Verbose, TEXT("Disallowed Tab: %s"), *TabType.ToString());
}
return bIsAllowed;
}
bool FTabManager::IsTabAllowedInSidebar(const FTabId TabId) const
{
if (const TSharedPtr<const FTabSpawnerEntry> Spawner = FindTabSpawnerFor(TabId.TabType))
{
return Spawner->CanSidebarTab();
}
return false;
}
void FTabManager::ToggleSidebarOpenTabs()
{
if(TemporarilySidebaredTabs.Num() == 0)
{
// Sidebar opened tabs not in a sidebar already
for (int32 AreaIndex = 0; AreaIndex < DockAreas.Num(); ++AreaIndex)
{
TSharedPtr<SDockingArea> SomeDockArea = DockAreas[AreaIndex].Pin();
if (SomeDockArea.IsValid() && SomeDockArea->CanHaveSidebar())
{
TArray<TSharedRef<SDockTab>> AllTabs = SomeDockArea->GetAllChildTabs();
for (TSharedRef<SDockTab>& Tab : AllTabs)
{
if (IsTabAllowedInSidebar(Tab->GetLayoutIdentifier()) && !SomeDockArea->IsTabInSidebar(Tab) && Tab->GetParentDockTabStack()->CanMoveTabToSideBar(Tab))
{
Tab->GetParentDockTabStack()->MoveTabToSidebar(Tab);
TemporarilySidebaredTabs.Add(Tab);
}
}
}
}
}
else
{
for (TWeakPtr<SDockTab>& TabPtr : TemporarilySidebaredTabs)
{
if (TSharedPtr<SDockTab> Tab = TabPtr.Pin())
{
Tab->GetParentDockTabStack()->GetDockArea()->RestoreTabFromSidebar(Tab.ToSharedRef());
}
}
TemporarilySidebaredTabs.Empty();
}
}
TSharedPtr<SDockTab> FTabManager::SpawnTab(const FTabId& TabId, const TSharedPtr<SWindow>& ParentWindow, const bool bCanOutputBeNullptr)
{
TSharedPtr<SDockTab> NewTabWidget;
// Whether or not the spawner overrode the ability for the tab to even spawn. This is not a failure case.
bool bSpawningAllowedBySpawner = true;
// Do we know how to spawn such a tab?
TSharedPtr<FTabSpawnerEntry> Spawner = FindTabSpawnerFor(TabId.TabType);
if ( Spawner.IsValid() )
{
if (Spawner->CanSpawnTab.IsBound())
{
bSpawningAllowedBySpawner = Spawner->CanSpawnTab.Execute(FSpawnTabArgs(ParentWindow, TabId));
}
if (bSpawningAllowedBySpawner && (!Spawner->SpawnedTabPtr.IsValid() || Spawner->OnFindTabToReuse.IsBound()))
{
NewTabWidget = Spawner->OnSpawnTab.Execute(FSpawnTabArgs(ParentWindow, TabId));
if(PendingMainNonClosableTab && NewTabWidget == PendingMainNonClosableTab)
{
PendingMainNonClosableTab = nullptr;
MainNonCloseableTabID = TabId;
}
if (FGlobalTabmanager::Get()->GetShouldUseMiddleEllipsisForDockTabLabel())
{
NewTabWidget->SetTabLabelOverflowPolicy(ETextOverflowPolicy::MiddleEllipsis);
}
NewTabWidget->SetLayoutIdentifier(TabId);
NewTabWidget->ProvideDefaultLabel(Spawner->GetDisplayName().IsEmpty() ? FText::FromName(Spawner->TabType) : Spawner->GetDisplayName());
NewTabWidget->ProvideDefaultIcon(Spawner->GetIcon().GetIcon());
// The spawner tracks that last tab it spawned
Spawner->SpawnedTabPtr = NewTabWidget;
}
else
{
// If we got here, somehow there is two entries spawning the same tab. This is now allowed so just ignore it.
bSpawningAllowedBySpawner = false;
}
}
// The tab was allowed to be spawned but failed for some reason
if (bSpawningAllowedBySpawner && !NewTabWidget.IsValid())
{
// We don't know how to spawn this tab. 2 alternatives:
// 1) Make a dummy tab so that things aren't entirely broken (previous versions of UE did this in all cases).
// 2) Do not open the widget and return nullptr, but keep the unknown widget saved in the layout. E.g., applied when calling RestoreFrom() from MainFrameModule.
FString StringToDisplay = (Spawner.IsValid() && !Spawner->GetDisplayName().IsEmpty() ? Spawner->GetDisplayName().ToString() : TabId.TabType.ToString());
if (StringToDisplay.IsEmpty())
{
StringToDisplay = FString("Unknown");
}
// If an output must be generated, create an "unrecognized tab" and log it
if (!bCanOutputBeNullptr)
{
UE_LOG(LogSlate, Log,
TEXT("The tab \"%s\" attempted to spawn in layout '%s' but failed for some reason. An \"unrecognized tab\" will be returned instead."), *StringToDisplay, *ActiveLayoutName.ToString()
);
NewTabWidget = SNew(SDockTab)
.Label( TabId.ToText() )
.ShouldAutosize( false )
[
SNew(SBox)
.HAlign(HAlign_Center)
.VAlign(VAlign_Center)
[
SNew(STextBlock)
.Text( NSLOCTEXT("TabManagement", "Unrecognized", "unrecognized tab") )
]
];
const FTabId UnrecognizedId(FName(TEXT("Unrecognized")), ETabIdFlags::None);
NewTabWidget->SetLayoutIdentifier(UnrecognizedId);
}
// If we can return nullptr, log it
else
{
UE_LOG(LogSlate, Log,
TEXT("The tab \"%s\" attempted to spawn in layout '%s' but failed for some reason. It will not be displayed."), *StringToDisplay, *ActiveLayoutName.ToString()
);
}
}
if (NewTabWidget.IsValid())
{
NewTabWidget->SetTabManager(SharedThis(this));
}
return NewTabWidget;
}
TSharedPtr<SDockTab> FTabManager::FindExistingLiveTab( const FTabId& TabId ) const
{
for ( int32 AreaIndex = 0; AreaIndex < DockAreas.Num(); ++AreaIndex )
{
const TSharedPtr<SDockingArea> SomeDockArea = DockAreas[ AreaIndex ].Pin();
if ( SomeDockArea.IsValid() )
{
TArray< TSharedRef<SDockTab> > ChildTabs = SomeDockArea->GetAllChildTabs();
ChildTabs.Append(SomeDockArea->GetAllSidebarTabs());
for (int32 ChildTabIndex=0; ChildTabIndex < ChildTabs.Num(); ++ChildTabIndex)
{
if ( TabId == ChildTabs[ChildTabIndex]->GetLayoutIdentifier() )
{
return ChildTabs[ChildTabIndex];
}
}
}
}
return TSharedPtr<SDockTab>();
}
FTabManager::~FTabManager()
{
ClearPendingLayoutSave();
}
TSharedPtr<SDockTab> FTabManager::FindLastTabInWindow(TSharedPtr<SWindow> Window) const
{
if ( Window.IsValid() )
{
for ( int32 AreaIndex = 0; AreaIndex < DockAreas.Num(); ++AreaIndex )
{
const TSharedPtr<SDockingArea> SomeDockArea = DockAreas[AreaIndex].Pin();
if ( SomeDockArea.IsValid() )
{
if ( SomeDockArea->GetParentWindow() == Window )
{
TArray< TSharedRef<SDockTab> > ChildTabs = SomeDockArea->GetAllChildTabs();
if ( ChildTabs.Num() > 0 )
{
return ChildTabs[ChildTabs.Num() - 1];
}
}
}
}
}
return TSharedPtr<SDockTab>();
}
TSharedPtr<class SDockingTabStack> FTabManager::FindTabInLiveAreas( const FTabMatcher& TabMatcher ) const
{
for ( int32 AreaIndex = 0; AreaIndex < DockAreas.Num(); ++AreaIndex )
{
const TSharedPtr<SDockingArea> SomeDockArea = DockAreas[ AreaIndex ].Pin();
if (SomeDockArea.IsValid())
{
TSharedPtr<SDockingTabStack> TabFoundHere = FindTabInLiveArea(TabMatcher, SomeDockArea.ToSharedRef());
if ( TabFoundHere.IsValid() )
{
return TabFoundHere;
}
}
}
return TSharedPtr<SDockingTabStack>();
}
TSharedPtr<class SDockingTabStack> FTabManager::FindTabInLiveArea( const FTabMatcher& TabMatcher, const TSharedRef<SDockingArea>& InArea )
{
TArray< TSharedRef<SDockingTabStack> > AllTabStacks;
GetAllStacks(InArea, AllTabStacks);
for (int32 StackIndex = 0; StackIndex < AllTabStacks.Num(); ++StackIndex)
{
if (AllTabStacks[StackIndex]->HasTab(TabMatcher))
{
return AllTabStacks[StackIndex];
}
}
return TSharedPtr<SDockingTabStack>();
}
FVector2D FTabManager::GetDefaultTabWindowSize(const FTabId& TabId)
{
FVector2D WindowSize = FTabManager::FallbackWindowSize;
FVector2D* DefaultTabSize = FTabManager::DefaultTabWindowSizeMap.Find(TabId);
if (DefaultTabSize != nullptr)
{
WindowSize = *DefaultTabSize;
}
return WindowSize;
}
bool FTabManager::HasAnyTabWithTabId( const TSharedRef<FLayoutNode>& SomeNode, const FName& InTabTypeToMatch ) const
{
return HasAnyMatchingTabs(SomeNode,
[this, InTabTypeToMatch](const FTab& Candidate)
{
return this->IsValidTabForSpawning(Candidate) && Candidate.TabId.TabType == InTabTypeToMatch;
});
}
TSharedPtr<FTabManager::FArea> FTabManager::GetAreaFromInitialLayoutWithTabType( const FTabId& InTabIdToMatch ) const
{
const TSharedPtr<FTabManager::FLayout> InitialLayoutSP = FGlobalTabmanager::Get()->GetInitialLayoutSP();
if (InitialLayoutSP.IsValid())
{
for (const TSharedRef<FArea>& Area : InitialLayoutSP->Areas)
{
if (HasAnyTabWithTabId(Area, InTabIdToMatch.TabType))
{
return Area.ToSharedPtr();
}
}
}
return nullptr;
}
TSharedRef<FTabManager::FArea> FTabManager::GetAreaForTabId(const FTabId& TabId)
{
if (const TSharedPtr<FArea> AreaFromInitiallyLoadedLayout = FGlobalTabmanager::Get()->GetAreaFromInitialLayoutWithTabType(TabId))
{
/* we must reuse positions from the initial layout for positionally specified floating windows. If we don't
* do this then any persisted floating windows load in a big cluster in the middle on top of one another */
if ( AreaFromInitiallyLoadedLayout->DefinesPositionallySpecifiedFloatingWindow() )
{
return AreaFromInitiallyLoadedLayout.ToSharedRef();
}
}
return NewArea( GetDefaultTabWindowSize(TabId) );
}
void FGlobalTabmanager::SetInitialLayoutSP(TSharedPtr<FTabManager::FLayout> InLayout)
{
InitialLayoutSP = InLayout;
}
TSharedPtr<FTabManager::FLayout> FGlobalTabmanager::GetInitialLayoutSP()
{
return InitialLayoutSP;
}
bool FTabManager::HasAnyMatchingTabs( const TSharedRef<FTabManager::FLayoutNode>& SomeNode, const TFunctionRef<bool(const FTab& Candidate)>& Matcher )
{
TSharedPtr<FTabManager::FSplitter> AsSplitter = SomeNode->AsSplitter();
TSharedPtr<FTabManager::FStack> AsStack = SomeNode->AsStack();
if ( AsStack.IsValid() )
{
return INDEX_NONE != AsStack->Tabs.IndexOfByPredicate(Matcher);
}
else
{
ensure( AsSplitter.IsValid() );
// Do any of the child nodes have open tabs?
for (int32 ChildIndex=0; ChildIndex < AsSplitter->ChildNodes.Num(); ++ChildIndex)
{
if ( HasAnyMatchingTabs(AsSplitter->ChildNodes[ChildIndex], Matcher) )
{
return true;
}
}
return false;
}
}
bool FTabManager::HasValidOpenTabs( const TSharedRef<FTabManager::FLayoutNode>& SomeNode ) const
{
// Search for valid and open tabs
return HasAnyMatchingTabs(SomeNode,
[this](const FTab& Candidate)
{
return this->IsValidTabForSpawning(Candidate) && Candidate.TabState == ETabState::OpenedTab;
});
}
bool FTabManager::HasValidTabs( const TSharedRef<FTabManager::FLayoutNode>& SomeNode ) const
{
// Search for valid tabs that can be spawned
return HasAnyMatchingTabs(SomeNode,
[this](const FTab& Candidate)
{
return this->IsValidTabForSpawning(Candidate);
});
}
void FTabManager::SetTabsTo(const TSharedRef<FTabManager::FLayoutNode>& SomeNode, const ETabState::Type NewTabState, const ETabState::Type OriginalTabState) const
{
// Set particular tab to desired NewTabState
TSharedPtr<FTabManager::FStack> AsStack = SomeNode->AsStack();
if (AsStack.IsValid())
{
TArray<FTab>& Tabs = AsStack->Tabs;
for (int32 TabIndex = 0; TabIndex < Tabs.Num(); ++TabIndex)
{
if (Tabs[TabIndex].TabState == OriginalTabState)
{
Tabs[TabIndex].TabState = NewTabState;
}
}
}
// Recursively set all tabs to desired NewTabState
else
{
TSharedPtr<FTabManager::FSplitter> AsSplitter = SomeNode->AsSplitter();
ensure(AsSplitter.IsValid());
for (int32 ChildIndex = 0; ChildIndex < AsSplitter->ChildNodes.Num(); ++ChildIndex)
{
SetTabsTo(AsSplitter->ChildNodes[ChildIndex], NewTabState, OriginalTabState);
}
}
}
void FTabManager::OnTabForegrounded( const TSharedPtr<SDockTab>& NewForegroundTab, const TSharedPtr<SDockTab>& BackgroundedTab )
{
// Do nothing.
}
void FTabManager::OnTabRelocated( const TSharedRef<SDockTab>& RelocatedTab, const TSharedPtr<SWindow>& NewOwnerWindow )
{
RelocatedTab->NotifyTabRelocated();
CleanupPointerArray(DockAreas);
RemoveTabFromCollapsedAreas( FTabMatcher( RelocatedTab->GetLayoutIdentifier() ) );
for (int32 DockAreaIndex=0; DockAreaIndex < DockAreas.Num(); ++DockAreaIndex)
{
DockAreas[DockAreaIndex].Pin()->OnTabFoundNewHome( RelocatedTab, NewOwnerWindow.ToSharedRef() );
}
FGlobalTabmanager::Get()->UpdateMainMenu(RelocatedTab, true);
UpdateStats();
RequestSavePersistentLayout();
if (TSharedPtr<FTabManager> NewTabManager = RelocatedTab->GetTabManagerPtr())
{
NewTabManager->RequestSavePersistentLayout();
}
}
void FTabManager::OnTabOpening( const TSharedRef<SDockTab>& TabBeingOpened )
{
UpdateStats();
RequestSavePersistentLayout();
}
void FTabManager::OnTabClosing( const TSharedRef<SDockTab>& TabBeingClosed )
{
RequestSavePersistentLayout();
}
void FTabManager::OnTabManagerClosing()
{
CleanupPointerArray(DockAreas);
// Gather the persistent layout and allow a custom handler to persist it
SavePersistentLayout();
for (int32 DockAreaIndex=0; DockAreaIndex < DockAreas.Num(); ++DockAreaIndex)
{
TSharedRef<SDockingArea> ChildDockArea = DockAreas[DockAreaIndex].Pin().ToSharedRef();
TSharedPtr<SWindow> DockAreaWindow = ChildDockArea->GetParentWindow();
if (DockAreaWindow.IsValid())
{
DockAreaWindow->RequestDestroyWindow();
}
}
}
bool FTabManager::CanCloseManager( const TSet< TSharedRef<SDockTab> >& TabsToIgnore )
{
CleanupPointerArray(DockAreas);
bool bCanCloseManager = true;
for (int32 DockAreaIndex=0; bCanCloseManager && DockAreaIndex < DockAreas.Num(); ++DockAreaIndex)
{
TSharedPtr<SDockingArea> SomeArea = DockAreas[DockAreaIndex].Pin();
TArray< TSharedRef<SDockTab> > AreasTabs = SomeArea.IsValid() ? SomeArea->GetAllChildTabs() : TArray< TSharedRef<SDockTab> >();
for (int32 TabIndex=0; bCanCloseManager && TabIndex < AreasTabs.Num(); ++TabIndex)
{
bCanCloseManager =
TabsToIgnore.Contains( AreasTabs[TabIndex] ) ||
AreasTabs[TabIndex]->GetTabRole() != ETabRole::MajorTab ||
AreasTabs[TabIndex]->CanCloseTab();
}
}
return bCanCloseManager;
}
void FTabManager::GetAllStacks( const TSharedRef<SDockingArea>& InDockArea, TArray< TSharedRef<SDockingTabStack> >& OutTabStacks )
{
TArray< TSharedRef<SDockingNode> > AllNodes = InDockArea->GetChildNodesRecursively();
for (int32 NodeIndex=0; NodeIndex < AllNodes.Num(); ++NodeIndex)
{
if ( AllNodes[NodeIndex]->GetNodeType() == SDockingNode::DockTabStack )
{
OutTabStacks.Add( StaticCastSharedRef<SDockingTabStack>( AllNodes[NodeIndex] ) );
}
}
}
TSharedPtr<FTabManager::FStack> FTabManager::FindTabUnderNode( const FTabMatcher& Matcher, const TSharedRef<FTabManager::FLayoutNode>& NodeToSearchUnder )
{
TSharedPtr<FTabManager::FStack> NodeAsStack = NodeToSearchUnder->AsStack();
TSharedPtr<FTabManager::FSplitter> NodeAsSplitter = NodeToSearchUnder->AsSplitter();
if (NodeAsStack.IsValid())
{
const int32 TabIndex = NodeAsStack->Tabs.IndexOfByPredicate(Matcher);
if (TabIndex != INDEX_NONE)
{
return NodeAsStack;
}
else
{
return TSharedPtr<FTabManager::FStack>();
}
}
else
{
ensure( NodeAsSplitter.IsValid() );
TSharedPtr<FTabManager::FStack> StackWithTab;
for ( int32 ChildIndex=0; !StackWithTab.IsValid() && ChildIndex < NodeAsSplitter->ChildNodes.Num(); ++ChildIndex)
{
StackWithTab = FindTabUnderNode( Matcher, NodeAsSplitter->ChildNodes[ChildIndex] );
}
return StackWithTab;
}
}
TSharedPtr<FTabSpawnerEntry> FTabManager::FindTabSpawnerFor(FName TabId)
{
// Look for a spawner in this tab manager.
TSharedRef<FTabSpawnerEntry>* Spawner = TabSpawner.Find(TabId);
if (Spawner == nullptr)
{
Spawner = NomadTabSpawner->Find(TabId);
}
return (Spawner != nullptr)
? TSharedPtr<FTabSpawnerEntry>(*Spawner)
: TSharedPtr<FTabSpawnerEntry>();
}
const TSharedPtr<const FTabSpawnerEntry> FTabManager::FindTabSpawnerFor(FName TabId) const
{
// Look for a spawner in this tab manager.
const TSharedRef<FTabSpawnerEntry>* Spawner = TabSpawner.Find(TabId);
if (Spawner == nullptr)
{
Spawner = NomadTabSpawner->Find(TabId);
}
return (Spawner != nullptr)
? TSharedPtr<const FTabSpawnerEntry>(*Spawner)
: TSharedPtr<const FTabSpawnerEntry>();
}
int32 FTabManager::FindTabInCollapsedAreas( const FTabMatcher& Matcher )
{
for ( int32 CollapsedDockAreaIndex=0; CollapsedDockAreaIndex < CollapsedDockAreas.Num(); ++CollapsedDockAreaIndex )
{
TSharedPtr<FTabManager::FStack> StackWithMatchingTab = FindTabUnderNode(Matcher, CollapsedDockAreas[CollapsedDockAreaIndex]);
if (StackWithMatchingTab.IsValid())
{
return CollapsedDockAreaIndex;
}
}
return INDEX_NONE;
}
void FTabManager::RemoveTabFromCollapsedAreas( const FTabMatcher& Matcher )
{
for ( int32 CollapsedDockAreaIndex=0; CollapsedDockAreaIndex < CollapsedDockAreas.Num(); ++CollapsedDockAreaIndex )
{
const TSharedRef<FTabManager::FArea>& DockArea = CollapsedDockAreas[CollapsedDockAreaIndex];
TSharedPtr<FTabManager::FStack> StackWithMatchingTab;
do
{
StackWithMatchingTab = FindTabUnderNode(Matcher, DockArea);
if (StackWithMatchingTab.IsValid())
{
const int32 TabIndex = StackWithMatchingTab->Tabs.IndexOfByPredicate(Matcher);
if ( ensure(TabIndex != INDEX_NONE) )
{
StackWithMatchingTab->Tabs.RemoveAt(TabIndex);
}
}
}
while ( StackWithMatchingTab.IsValid() );
}
}
void FTabManager::UpdateStats()
{
StaticCastSharedRef<FTabManager>(FGlobalTabmanager::Get())->UpdateStats();
}
void FTabManager::GetRecordableStats( int32& OutTabCount, TArray<TSharedPtr<SWindow>>& OutUniqueParentWindows ) const
{
OutTabCount = 0;
for (auto AreaIter = DockAreas.CreateConstIterator(); AreaIter; ++AreaIter)
{
TSharedPtr<SDockingArea> DockingArea = AreaIter->Pin();
if (DockingArea.IsValid())
{
TSharedPtr<SWindow> ParentWindow = DockingArea->GetParentWindow();
if (ParentWindow.IsValid())
{
OutUniqueParentWindows.AddUnique(ParentWindow);
}
TArray< TSharedRef<SDockingTabStack> > OutTabStacks;
GetAllStacks(DockingArea.ToSharedRef(), OutTabStacks);
for (auto StackIter = OutTabStacks.CreateConstIterator(); StackIter; ++StackIter)
{
OutTabCount += (*StackIter)->GetNumTabs();
}
}
}
}
const TSharedRef<FGlobalTabmanager>& FGlobalTabmanager::Get()
{
static const TSharedRef<FGlobalTabmanager> Instance = FGlobalTabmanager::New();
// @todo: Never Destroy the Global Tab Manager because it has hooks into a bunch of different modules.
// All those modules are unloaded first, so unbinding the delegates will cause a problem.
static const TSharedRef<FGlobalTabmanager>* NeverDestroyGlobalTabManager = new TSharedRef<FGlobalTabmanager>( Instance );
return Instance;
}
bool FGlobalTabmanager::GetShouldUseMiddleEllipsisForDockTabLabel() const
{
return bShouldUseMiddleEllipsisForDockTabLabel;
}
void FGlobalTabmanager::SetShouldUseMiddleEllipsisForDockTabLabel(const bool bInShouldUseMiddleEllipsis)
{
bShouldUseMiddleEllipsisForDockTabLabel = bInShouldUseMiddleEllipsis;
}
FDelegateHandle FGlobalTabmanager::OnActiveTabChanged_Subscribe( const FOnActiveTabChanged::FDelegate& InDelegate )
{
return OnActiveTabChanged.Add( InDelegate );
}
void FGlobalTabmanager::OnActiveTabChanged_Unsubscribe( FDelegateHandle Handle )
{
OnActiveTabChanged.Remove( Handle );
}
FDelegateHandle FGlobalTabmanager::OnTabForegrounded_Subscribe(const FOnActiveTabChanged::FDelegate& InDelegate)
{
return TabForegrounded.Add(InDelegate);
}
void FGlobalTabmanager::OnTabForegrounded_Unsubscribe(FDelegateHandle Handle)
{
TabForegrounded.Remove(Handle);
}
TSharedPtr<class SDockTab> FGlobalTabmanager::GetActiveTab() const
{
return ActiveTabPtr.Pin();
}
bool FGlobalTabmanager::CanSetAsActiveTab(const TSharedPtr<SDockTab>& Tab)
{
// Setting NULL wipes out the active tab; always apply that change.
// Major tabs are ignored for the purposes of active-tab tracking. We do not care about their
return !Tab.IsValid() || Tab->GetVisualTabRole() != ETabRole::MajorTab;
}
void FGlobalTabmanager::SetActiveTab( const TSharedPtr<class SDockTab>& NewActiveTab )
{
const bool bShouldApplyChange = CanSetAsActiveTab(NewActiveTab);
TSharedPtr<SDockTab> CurrentlyActiveTab = GetActiveTab();
if (bShouldApplyChange && (CurrentlyActiveTab != NewActiveTab))
{
if (NewActiveTab.IsValid())
{
NewActiveTab->UpdateActivationTime();
}
OnActiveTabChanged.Broadcast( CurrentlyActiveTab, NewActiveTab );
ActiveTabPtr = NewActiveTab;
}
}
FTabSpawnerEntry& FGlobalTabmanager::RegisterNomadTabSpawner(const FName TabId, const FOnSpawnTab& OnSpawnTab, const FCanSpawnTab& CanSpawnTab)
{
// Sanity check
ensure(!IsLegacyTabType(TabId));
LLM_SCOPE_BYTAG(UI_Slate);
// Remove TabId if it was previously loaded. This allows re-loading the Editor UI layout without restarting the whole Editor (Window->Load Layout)
if (NomadTabSpawner->Contains(TabId))
{
UnregisterNomadTabSpawner(TabId);
}
// (Re)create and return NewSpawnerEntry
TSharedRef<FTabSpawnerEntry> NewSpawnerEntry = MakeShareable(new FTabSpawnerEntry(TabId, OnSpawnTab, CanSpawnTab));
NomadTabSpawner->Add(TabId, NewSpawnerEntry);
return NewSpawnerEntry.Get();
}
void FGlobalTabmanager::UnregisterNomadTabSpawner( const FName TabId )
{
const int32 NumRemoved = NomadTabSpawner->Remove(TabId);
}
void FGlobalTabmanager::SetApplicationTitle( const FText& InAppTitle )
{
AppTitle = InAppTitle;
for (int32 DockAreaIndex=0; DockAreaIndex < DockAreas.Num(); ++DockAreaIndex)
{
if (DockAreas[DockAreaIndex].IsValid())
{
TSharedPtr<SWindow> ParentWindow = DockAreas[DockAreaIndex].Pin()->GetParentWindow();
if (ParentWindow.IsValid() && ParentWindow == FGlobalTabmanager::Get()->GetRootWindow())
{
ParentWindow->SetTitle( AppTitle );
}
}
}
}
const FText& FGlobalTabmanager::GetApplicationTitle() const
{
return AppTitle;
}
bool FGlobalTabmanager::CanCloseManager( const TSet< TSharedRef<SDockTab> >& TabsToIgnore )
{
bool bCanCloseManager = FTabManager::CanCloseManager(TabsToIgnore);
for( int32 ManagerIndex=0; bCanCloseManager && ManagerIndex < SubTabManagers.Num(); ++ManagerIndex )
{
TSharedPtr<FTabManager> SubManager = SubTabManagers[ManagerIndex].TabManager.Pin();
if (SubManager.IsValid())
{
bCanCloseManager = SubManager->CanCloseManager(TabsToIgnore);
}
}
return bCanCloseManager;
}
TSharedPtr<SDockTab> FGlobalTabmanager::GetMajorTabForTabManager(const TSharedRef<FTabManager>& ChildManager)
{
const int32 MajorTabIndex = SubTabManagers.IndexOfByPredicate(FindByManager(ChildManager));
if ( MajorTabIndex != INDEX_NONE )
{
return SubTabManagers[MajorTabIndex].MajorTab.Pin();
}
return TSharedPtr<SDockTab>();
}
TSharedPtr<FTabManager> FGlobalTabmanager::GetTabManagerForMajorTab(const TSharedPtr<SDockTab> DockTab) const
{
const int32 Index = SubTabManagers.IndexOfByPredicate(FindByTab(DockTab.ToSharedRef()));
if (Index != INDEX_NONE)
{
return SubTabManagers[Index].TabManager.Pin();
}
return nullptr;
}
void FGlobalTabmanager::DrawAttentionToTabManager( const TSharedRef<FTabManager>& ChildManager )
{
TSharedPtr<SDockTab> Tab = GetMajorTabForTabManager(ChildManager);
if ( Tab.IsValid() )
{
this->DrawAttention(Tab.ToSharedRef());
// #HACK VREDITOR
if ( ProxyTabManager.IsValid() )
{
if( ProxyTabManager->IsTabSupported( Tab->GetLayoutIdentifier() ) )
{
ProxyTabManager->DrawAttention(Tab.ToSharedRef());
}
}
}
}
TSharedRef<FTabManager> FGlobalTabmanager::NewTabManager( const TSharedRef<SDockTab>& InOwnerTab )
{
struct {
bool operator()(const FSubTabManager& InItem) const
{
return !InItem.MajorTab.IsValid();
}
} ShouldRemove;
SubTabManagers.RemoveAll( ShouldRemove );
const TSharedRef<FTabManager> NewTabManager = FTabManager::New( InOwnerTab, NomadTabSpawner );
SubTabManagers.Add( FSubTabManager(InOwnerTab, NewTabManager) );
UpdateStats();
return NewTabManager;
}
void FGlobalTabmanager::UpdateMainMenu(const TSharedRef<SDockTab>& ForTab, bool const bForce)
{
TSharedPtr<FTabManager> TabManager = ForTab->GetTabManagerPtr();
if(TabManager == AsShared())
{
const int32 TabIndex = SubTabManagers.IndexOfByPredicate(FindByTab(ForTab));
if (TabIndex != INDEX_NONE)
{
TabManager = SubTabManagers[TabIndex].TabManager.Pin();
}
}
TabManager->UpdateMainMenu(ForTab, bForce);
}
void FGlobalTabmanager::SaveAllVisualState()
{
this->SavePersistentLayout();
for( int32 ManagerIndex=0; ManagerIndex < SubTabManagers.Num(); ++ManagerIndex )
{
const TSharedPtr<FTabManager> SubManagerTab = SubTabManagers[ManagerIndex].TabManager.Pin();
if (SubManagerTab.IsValid())
{
SubManagerTab->SavePersistentLayout();
}
}
}
void FGlobalTabmanager::SetRootWindow( const TSharedRef<SWindow> InRootWindow )
{
RootWindowPtr = InRootWindow;
}
TSharedPtr<SWindow> FGlobalTabmanager::GetRootWindow() const
{
return RootWindowPtr.Pin();
}
void FGlobalTabmanager::AddLegacyTabType(FName InLegacyTabType, FName InNewTabType)
{
ensure(!TabSpawner.Contains(InLegacyTabType));
ensure(!NomadTabSpawner->Contains(InLegacyTabType));
LegacyTabTypeRedirectionMap.Add(InLegacyTabType, InNewTabType);
}
bool FGlobalTabmanager::IsLegacyTabType(FName InTabType) const
{
return LegacyTabTypeRedirectionMap.Contains(InTabType);
}
FName FGlobalTabmanager::GetTabTypeForPotentiallyLegacyTab(FName InTabType) const
{
const FName* NewTabType = LegacyTabTypeRedirectionMap.Find(InTabType);
return NewTabType ? *NewTabType : InTabType;
}
void FGlobalTabmanager::OnTabForegrounded( const TSharedPtr<SDockTab>& NewForegroundTab, const TSharedPtr<SDockTab>& BackgroundedTab )
{
if (NewForegroundTab.IsValid())
{
// Show any child windows associated with the Major Tab that got foregrounded.
const int32 ForegroundedTabIndex = SubTabManagers.IndexOfByPredicate(FindByTab(NewForegroundTab.ToSharedRef()));
if (ForegroundedTabIndex != INDEX_NONE)
{
TSharedPtr<FTabManager> ForegroundTabManager = SubTabManagers[ForegroundedTabIndex].TabManager.Pin();
ForegroundTabManager->GetPrivateApi().ShowWindows();
}
NewForegroundTab->UpdateActivationTime();
}
if (BackgroundedTab.IsValid())
{
// Hide any child windows associated with the Major Tab that got backgrounded.
const int32 BackgroundedTabIndex = SubTabManagers.IndexOfByPredicate(FindByTab(BackgroundedTab.ToSharedRef()));
if (BackgroundedTabIndex != INDEX_NONE)
{
TSharedPtr<FTabManager> BackgroundedTabManager = SubTabManagers[BackgroundedTabIndex].TabManager.Pin();
BackgroundedTabManager->GetPrivateApi().HideWindows();
}
}
TabForegrounded.Broadcast(NewForegroundTab, BackgroundedTab);
}
void FGlobalTabmanager::OnTabRelocated( const TSharedRef<SDockTab>& RelocatedTab, const TSharedPtr<SWindow>& NewOwnerWindow )
{
if ( RelocatedTab->GetTabRole() == ETabRole::MajorTab || RelocatedTab->GetTabRole() == ETabRole::NomadTab )
{
LastMajorDockWindow = NewOwnerWindow;
}
if (NewOwnerWindow.IsValid())
{
const int32 RelocatedManagerIndex = SubTabManagers.IndexOfByPredicate(FindByTab(RelocatedTab));
if (RelocatedManagerIndex != INDEX_NONE)
{
const TSharedRef<FTabManager>& RelocatedManager = SubTabManagers[RelocatedManagerIndex].TabManager.Pin().ToSharedRef();
// Reparent any DockAreas hanging out in a child window.
// We do not support native window re-parenting, so destroy old windows and re-create new ones in their place that are properly parented.
// Move the old DockAreas into new windows.
const TArray< TWeakPtr<SDockingArea> >& LiveDockAreas = RelocatedManager->GetPrivateApi().GetLiveDockAreas();
for (int32 DockAreaIndex=0; DockAreaIndex < LiveDockAreas.Num(); ++DockAreaIndex)
{
const TSharedRef<SDockingArea>& ChildDockArea = LiveDockAreas[ DockAreaIndex ].Pin().ToSharedRef();
const TSharedPtr<SWindow> OldChildWindow = ChildDockArea->GetParentWindow();
if ( OldChildWindow.IsValid() )
{
TSharedRef<SWindow> NewChildWindow = SNew(SWindow)
.AutoCenter(EAutoCenter::None)
.ScreenPosition(OldChildWindow->GetPositionInScreen())
.ClientSize(OldChildWindow->GetSizeInScreen())
.SupportsMinimize(false)
.SupportsMaximize(false)
.CreateTitleBar(false)
.AdjustInitialSizeAndPositionForDPIScale(false)
[
ChildDockArea
];
ChildDockArea->SetParentWindow(NewChildWindow);
FSlateApplication::Get().AddWindowAsNativeChild( NewChildWindow, NewOwnerWindow.ToSharedRef() );
FSlateApplication::Get().RequestDestroyWindow( OldChildWindow.ToSharedRef() );
}
}
}
#if WITH_EDITOR
// When a tab is relocated we need to let the content know that the dpi scale window where the tab now resides may have changed
FSlateApplication::Get().OnWindowDPIScaleChanged().Broadcast(NewOwnerWindow.ToSharedRef());
#endif
}
FTabManager::OnTabRelocated( RelocatedTab, NewOwnerWindow );
}
void FGlobalTabmanager::OnTabClosing( const TSharedRef<SDockTab>& TabBeingClosed )
{
FTabManager::OnTabClosing(TabBeingClosed);
// Is this a major tab that contained a Sub TabManager?
// If so, need to properly close the sub tab manager
const int32 TabManagerBeingClosedIndex = SubTabManagers.IndexOfByPredicate(FindByTab(TabBeingClosed));
if (TabManagerBeingClosedIndex != INDEX_NONE)
{
const TSharedRef<FTabManager>& TabManagerBeingClosed = SubTabManagers[TabManagerBeingClosedIndex].TabManager.Pin().ToSharedRef();
TabManagerBeingClosed->GetPrivateApi().OnTabManagerClosing();
}
}
void FGlobalTabmanager::OnTabManagerClosing()
{
for( int32 ManagerIndex=0; ManagerIndex < SubTabManagers.Num(); ++ManagerIndex )
{
TSharedPtr<SDockTab> SubManagerTab = SubTabManagers[ManagerIndex].MajorTab.Pin();
if (SubManagerTab.IsValid())
{
SubManagerTab->RemoveTabFromParent();
}
}
}
void FGlobalTabmanager::UpdateStats()
{
// Get all the tabs and windows in the global manager's own areas
int32 AllTabsCount = 0;
TArray<TSharedPtr<SWindow>> ParentWindows;
GetRecordableStats(AllTabsCount, ParentWindows);
// Add in all the tabs and windows in the sub-managers
for (auto ManagerIter = SubTabManagers.CreateConstIterator(); ManagerIter; ++ManagerIter)
{
if (ManagerIter->TabManager.IsValid())
{
int32 TabsCount = 0;
ManagerIter->TabManager.Pin()->GetRecordableStats(TabsCount, ParentWindows);
AllTabsCount += TabsCount;
}
}
// Keep a running maximum of the tab and window counts
AllTabsMaxCount = FMath::Max(AllTabsMaxCount, AllTabsCount);
AllAreasWindowMaxCount = FMath::Max(AllAreasWindowMaxCount, ParentWindows.Num());
}
void FGlobalTabmanager::OpenUnmanagedTab(FName PlaceholderId, const FSearchPreference& SearchPreference, const TSharedRef<SDockTab>& UnmanagedTab)
{
if ( ProxyTabManager.IsValid() && ProxyTabManager->IsTabSupported( UnmanagedTab->GetLayoutIdentifier() ) )
{
ProxyTabManager->OpenUnmanagedTab(PlaceholderId, SearchPreference, UnmanagedTab);
}
else
{
FTabManager::OpenUnmanagedTab(PlaceholderId, SearchPreference, UnmanagedTab);
}
}
void FGlobalTabmanager::FinishRestore()
{
for (FSubTabManager& SubManagerInfo : SubTabManagers)
{
if (TSharedPtr<FTabManager> Manager = SubManagerInfo.TabManager.Pin())
{
Manager->UpdateMainMenu(nullptr, false);
}
}
}
void FGlobalTabmanager::SetCanSavePersistentLayouts(bool bInCanSavePersistentLayouts)
{
bCanSavePersistentLayouts = bInCanSavePersistentLayouts;
}
bool FGlobalTabmanager::CanSavePersistentLayouts() const
{
return bCanSavePersistentLayouts;
}
void FGlobalTabmanager::SetProxyTabManager(TSharedPtr<FProxyTabmanager> InProxyTabManager)
{
ProxyTabManager = InProxyTabManager;
}
bool FProxyTabmanager::IsTabSupported( const FTabId TabId ) const
{
bool bIsTabSupported = true;
if( OnIsTabSupported.IsBound() )
{
OnIsTabSupported.Broadcast( TabId, /* In/Out */ bIsTabSupported );
}
return bIsTabSupported;
}
void FProxyTabmanager::OpenUnmanagedTab(FName PlaceholderId, const FSearchPreference& SearchPreference, const TSharedRef<SDockTab>& UnmanagedTab)
{
TSharedPtr<SWindow> ParentWindowPtr = ParentWindow.Pin();
if (ensure(ParentWindowPtr.IsValid()))
{
const TSharedPtr<FArea> Area = FGlobalTabmanager::Get()->GetAreaFromInitialLayoutWithTabType(UnmanagedTab->GetLayoutIdentifier());
const TSharedRef<FArea> NewAreaForTab = Area.IsValid() ? Area.ToSharedRef() : NewPrimaryArea();
NewAreaForTab
->Split
(
FTabManager::NewStack()
->AddTab( UnmanagedTab->GetLayoutIdentifier(), ETabState::OpenedTab )
);
if (TSharedPtr<SDockingArea> DockingArea = RestoreArea(NewAreaForTab, ParentWindowPtr))
{
ParentWindowPtr->SetContent(StaticCastSharedRef<SDockingArea>(DockingArea->AsShared()));
if (DockingArea->GetAllChildTabs().Num() > 0)
{
const TSharedPtr<SDockTab> NewlyOpenedTab = DockingArea->GetAllChildTabs()[0];
check(NewlyOpenedTab.IsValid());
NewlyOpenedTab->GetParent()->GetParentDockTabStack()->OpenTab(UnmanagedTab);
NewlyOpenedTab->RequestCloseTab();
MainNonCloseableTabID = UnmanagedTab->GetLayoutIdentifier();
OnTabOpened.Broadcast(UnmanagedTab);
}
}
}
}
void FProxyTabmanager::DrawAttention(const TSharedRef<SDockTab>& TabToHighlight)
{
FTabManager::DrawAttention(TabToHighlight);
OnAttentionDrawnToTab.Broadcast(TabToHighlight);
}
void FProxyTabmanager::SetParentWindow(TSharedRef<SWindow> InParentWindow)
{
ParentWindow = InParentWindow;
}
#undef LOCTEXT_NAMESPACE