// Copyright Epic Games, Inc. All Rights Reserved. #include "StatusBarSubsystem.h" #include "ToolMenus.h" #include "Framework/Docking/TabManager.h" #include "Framework/Application/SlateApplication.h" #include "ContentBrowserModule.h" #include "IContentBrowserSingleton.h" #include "SourceControlMenuHelpers.h" #include "SStatusBar.h" #include "Widgets/Docking/SDockTab.h" #include "Editor.h" #include "Toolkits/GlobalEditorCommonCommands.h" #include "Widgets/Notifications/SNotificationList.h" #include "Framework/Notifications/NotificationManager.h" #include "Interfaces/IMainFrameModule.h" #include "Widgets/Notifications/SNotificationBackground.h" #include "Brushes/SlateRoundedBoxBrush.h" #include "Styling/StyleColors.h" #include "Widgets/Images/SImage.h" #include "Widgets/Input/SButton.h" #include "Misc/ConfigCacheIni.h" #include "OutputLogModule.h" #include "WidgetDrawerConfig.h" #include "Widgets/Input/SMultiLineEditableTextBox.h" #include "OutputLogSettings.h" #include "SOneTimeIndustryQuery.h" #include "Types/SlateAttributeMetaData.h" #define LOCTEXT_NAMESPACE "StatusBar" DEFINE_LOG_CATEGORY_STATIC(LogStatusBar, Log, All); int32 UStatusBarSubsystem::MessageHandleCounter = 0; namespace UE { namespace StatusBarSubsystem { namespace Private { TSharedPtr FindParentWindow() { TSharedPtr ParentWindow = FSlateApplication::Get().GetActiveTopLevelWindow(); if (!ParentWindow.IsValid()) { if (TSharedPtr ActiveTab = FGlobalTabmanager::Get()->GetActiveTab()) { if (TSharedPtr ActiveTabManager = ActiveTab->GetTabManagerPtr()) { if (TSharedPtr ActiveMajorTab = FGlobalTabmanager::Get()->GetMajorTabForTabManager(ActiveTabManager.ToSharedRef())) { ParentWindow = ActiveMajorTab->GetParentWindow(); } } } } return ParentWindow; } TSharedPtr FindParentWindowBehindNotification() { TSharedPtr ParentWindow = FSlateApplication::Get().GetActiveTopLevelWindow(); if (!ParentWindow.IsValid()) { if (TSharedPtr ActiveTab = FGlobalTabmanager::Get()->GetActiveTab()) { if (TSharedPtr ActiveTabManager = ActiveTab->GetTabManagerPtr()) { if (TSharedPtr ActiveMajorTab = FGlobalTabmanager::Get()->GetMajorTabForTabManager(ActiveTabManager.ToSharedRef())) { ParentWindow = ActiveMajorTab->GetParentWindow(); } } } } else if (ParentWindow->GetType() == EWindowType::Notification) { // Get the parent window directly behind the notification. ParentWindow = FSlateApplication::Get().GetActiveTopLevelRegularWindow(); } return ParentWindow; } } } } class SNewUserTipNotification : public SCompoundWidget { SLATE_BEGIN_ARGS(SNewUserTipNotification) {} SLATE_END_ARGS() SNewUserTipNotification() : NewBadgeBrush(FStyleColors::Success) , KeybindBackgroundBrush(FLinearColor::Transparent, 6.0f, FStyleColors::ForegroundHover, 1.5f) {} static void Show(TSharedPtr InParentWindow) { if(!ActiveNotification.IsValid()) { TSharedRef ActiveNotificationRef = SNew(SNewUserTipNotification); ActiveNotification = ActiveNotificationRef; ParentWindow = InParentWindow; InParentWindow->AddOverlaySlot() .VAlign(VAlign_Bottom) .HAlign(HAlign_Left) .Padding(FMargin(20.0f, 20.0f, 10.0f, 50.f)) [ ActiveNotificationRef ]; } } static void Dismiss() { TSharedPtr ActiveNotificationPin = ActiveNotification.Pin(); if (ParentWindow.IsValid() && ActiveNotificationPin.IsValid()) { ParentWindow.Pin()->RemoveOverlaySlot(ActiveNotificationPin.ToSharedRef()); } ParentWindow.Reset(); ActiveNotification.Reset(); } void Construct(const FArguments& InArgs) { ChildSlot [ SNew(SBox) .WidthOverride(350.0f) .HeightOverride(128.0f) [ SNew(SNotificationBackground) .Padding(FMargin(16, 8)) [ SNew(SHorizontalBox) + SHorizontalBox::Slot() .Padding(0.0f, 6.0f, 0.0f, 0.0f) .VAlign(VAlign_Top) .AutoWidth() [ SNew(SBorder) .Padding(FMargin(11,4)) .BorderImage(&NewBadgeBrush) .ForegroundColor(FStyleColors::ForegroundInverted) [ SNew(STextBlock) .Text(LOCTEXT("NewBadge", "New")) .TextStyle(FAppStyle::Get(), "SmallButtonText") .ColorAndOpacity(FSlateColor::UseForeground()) ] ] + SHorizontalBox::Slot() .AutoWidth() .Padding(16.0f, 8.0f, 0.0f, 0.0f) [ SNew(SVerticalBox) + SVerticalBox::Slot() .AutoHeight() .Padding(0.0f, 0.0f, 0.0f, 0.0f) [ SNew(STextBlock) .Font(FAppStyle::Get().GetFontStyle("NotificationList.FontBold")) .Text(LOCTEXT("ContentDrawerTipTitle", "Content Drawer")) .ColorAndOpacity(FStyleColors::ForegroundHover) ] + SVerticalBox::Slot() .AutoHeight() .Padding(0.0f, 12.0f, 0.0f, 0.0f) [ SNew(SHorizontalBox) + SHorizontalBox::Slot() .AutoWidth() [ SNew(SBorder) .Padding(FMargin(20, 4)) .BorderImage(&KeybindBackgroundBrush) [ SNew(STextBlock) .TextStyle(FAppStyle::Get(), "DialogButtonText") .Text(FGlobalEditorCommonCommands::Get().OpenContentBrowserDrawer->GetActiveChord(EMultipleKeyBindingIndex::Primary)->GetModifierText(FText::GetEmpty())) .ColorAndOpacity(FStyleColors::ForegroundHover) ] ] + SHorizontalBox::Slot() .Padding(8.0f, 0.0f) .AutoWidth() .VAlign(VAlign_Center) [ SNew(SImage) .Image(FAppStyle::Get().GetBrush("Icons.Plus")) .ColorAndOpacity(FStyleColors::ForegroundHover) ] + SHorizontalBox::Slot() .AutoWidth() [ SNew(SBorder) .Padding(FMargin(20, 4)) .BorderImage(&KeybindBackgroundBrush) [ SNew(STextBlock) .TextStyle(FAppStyle::Get(), "DialogButtonText") .Text(FGlobalEditorCommonCommands::Get().OpenContentBrowserDrawer->GetActiveChord(EMultipleKeyBindingIndex::Primary)->Key.GetDisplayName(false)) .ColorAndOpacity(FStyleColors::ForegroundHover) ] ] ] + SVerticalBox::Slot() .AutoHeight() .Padding(0.0f, 12.0f, 0.0f, 0.0f) [ SNew(STextBlock) .Text(LOCTEXT("ContentDrawerTipDesc", "Summon the content browser in\ncollapsable drawer.")) .ColorAndOpacity(FStyleColors::Foreground) ] ] + SHorizontalBox::Slot() .Padding(0.0f, 0.0f, 0.0f, 0.0f) .HAlign(HAlign_Right) .VAlign(VAlign_Top) [ SNew(SButton) .ButtonStyle(FAppStyle::Get(), "SimpleButton") .OnClicked_Lambda([]() { SNewUserTipNotification::Dismiss(); return FReply::Handled(); }) [ SNew(SImage) .Image(FAppStyle::Get().GetBrush("Icons.X")) .ColorAndOpacity(FSlateColor::UseForeground()) ] ] ] ] ]; } private: FSlateRoundedBoxBrush NewBadgeBrush; FSlateRoundedBoxBrush KeybindBackgroundBrush; static TWeakPtr ActiveNotification; static TWeakPtr ParentWindow; }; TWeakPtr SNewUserTipNotification::ActiveNotification; TWeakPtr SNewUserTipNotification::ParentWindow; void UStatusBarSubsystem::Initialize(FSubsystemCollectionBase& Collection) { FSourceControlCommands::Register(); IMainFrameModule& MainFrameModule = IMainFrameModule::Get(); if (MainFrameModule.IsWindowInitialized()) { CreateAndShowNewUserTipIfNeeded(MainFrameModule.GetParentWindow(), false); CreateAndShowOneTimeIndustryQueryIfNeeded(MainFrameModule.GetParentWindow(), false); } else { MainFrameModule.OnMainFrameCreationFinished().AddUObject(this, &UStatusBarSubsystem::CreateAndShowNewUserTipIfNeeded); MainFrameModule.OnMainFrameCreationFinished().AddUObject(this, &UStatusBarSubsystem::CreateAndShowOneTimeIndustryQueryIfNeeded); } FSlateNotificationManager::Get().SetProgressNotificationHandler(this); } void UStatusBarSubsystem::Deinitialize() { FSourceControlCommands::Unregister(); FSlateNotificationManager::Get().SetProgressNotificationHandler(nullptr); StatusBarContentBrowser.Reset(); StatusBarOutputLog.Reset(); } bool UStatusBarSubsystem::ToggleDebugConsole(TSharedRef ParentWindow, bool bAlwaysToggleDrawer) { bool bToggledSuccessfully = false; FOutputLogModule& OutputLogModule = FModuleManager::Get().LoadModuleChecked("OutputLog"); // Get the global output log tab if it exists. If it exists and is in the same editor as the status bar we'll focus that instead TSharedPtr MainOutputLogTab = OutputLogModule.GetOutputLogTab(); const bool bCycleToOutputLogDrawer = OutputLogModule.ShouldCycleToOutputLogDrawer() || bAlwaysToggleDrawer; for (const TPair& StatusBar : StatusBars) { const FStatusBarData& SBData = StatusBar.Value; if (TSharedPtr StatusBarPinned = SBData.StatusBarWidget.Pin()) { TSharedPtr ParentTab = StatusBarPinned->GetParentTab(); if (ParentTab && ParentTab->IsForeground() && ParentTab->GetParentWindow() == ParentWindow) { FWidgetPath ConsoleEditBoxPath; if(FSlateApplication::Get().GeneratePathToWidgetUnchecked(SBData.ConsoleEditBox.ToSharedRef(), ConsoleEditBoxPath)) { if (bAlwaysToggleDrawer && MainOutputLogTab && MainOutputLogTab->GetTabManagerPtr() && MainOutputLogTab->GetTabManagerPtr()->GetOwnerTab() == ParentTab) { OutputLogModule.FocusOutputLogConsoleBox(OutputLogModule.GetOutputLog().ToSharedRef()); } else { // This toggles between 3 states: // If the drawer is opened, close it // if the console edit box is focused, open the drawer // if something else is focused, focus the console edit box if (StatusBarPinned->IsDrawerOpened(StatusBarDrawerIds::OutputLog)) { StatusBarPinned->DismissDrawer(nullptr); } else if (SBData.ConsoleEditBox->HasKeyboardFocus() || bAlwaysToggleDrawer) { if (bCycleToOutputLogDrawer) { StatusBarPinned->OpenDrawer(StatusBarDrawerIds::OutputLog); } else { // Restore previous focus FSlateApplication::Get().SetKeyboardFocus(PreviousKeyboardFocusedWidget.Pin(), EFocusCause::SetDirectly); PreviousKeyboardFocusedWidget.Reset(); } } else { // Cache off the previously focused widget so we can restore focus if the user hits the focus key again PreviousKeyboardFocusedWidget = FSlateApplication::Get().GetKeyboardFocusedWidget(); FSlateApplication::Get().SetKeyboardFocus(ConsoleEditBoxPath, EFocusCause::SetDirectly); } } bToggledSuccessfully = true; break; } } } } return bToggledSuccessfully; } bool UStatusBarSubsystem::OpenContentBrowserDrawer() { return TriggerContentBrowser(EDrawerTriggerMode::Open); } bool UStatusBarSubsystem::ToggleContentBrowserDrawer() { return TriggerContentBrowser(EDrawerTriggerMode::Toggle); } bool UStatusBarSubsystem::DismissContentBrowserDrawer() { return TriggerContentBrowser(EDrawerTriggerMode::Dismiss); } bool UStatusBarSubsystem::OpenOutputLogDrawer() { TSharedPtr ParentWindow = UE::StatusBarSubsystem::Private::FindParentWindowBehindNotification(); if (ParentWindow.IsValid() && ParentWindow->GetType() == EWindowType::Normal) { constexpr bool bAlwaysToggleDrawer = true; return ToggleDebugConsole(ParentWindow.ToSharedRef(), bAlwaysToggleDrawer); } return false; } bool UStatusBarSubsystem::TryToggleDrawer(const FName DrawerId) { bool bToggledSuccessfully = false; TSharedPtr ParentWindow = UE::StatusBarSubsystem::Private::FindParentWindowBehindNotification(); if (ParentWindow.IsValid() && ParentWindow->GetType() == EWindowType::Normal) { for (const TPair& StatusBar : StatusBars) { const FStatusBarData& SBData = StatusBar.Value; if (TSharedPtr StatusBarPinned = SBData.StatusBarWidget.Pin()) { TSharedPtr ParentTab = StatusBarPinned->GetParentTab(); if (ParentTab && ParentTab->IsForeground() && ParentTab->GetParentWindow() == ParentWindow) { if (StatusBarPinned->IsDrawerOpened(DrawerId)) { StatusBarPinned->DismissDrawer(nullptr); } else { StatusBarPinned->OpenDrawer(DrawerId); } bToggledSuccessfully = true; break; } } } } return bToggledSuccessfully; } bool UStatusBarSubsystem::ForceDismissDrawer() { bool bWasDismissed = false; for (const TPair& StatusBar : StatusBars) { if (TSharedPtr StatusBarPinned = StatusBar.Value.StatusBarWidget.Pin()) { bWasDismissed |= StatusBarPinned->DismissDrawer(nullptr); } } return bWasDismissed; } bool UStatusBarSubsystem::TriggerContentBrowser(EDrawerTriggerMode DrawerTriggerMode) { TSharedPtr ParentWindow = UE::StatusBarSubsystem::Private::FindParentWindowBehindNotification(); if (!ParentWindow) { return false; } bool bWasAlreadyOpened = false; SNewUserTipNotification::Dismiss(); for (const TPair& StatusBar : StatusBars) { if (TSharedPtr StatusBarPinned = StatusBar.Value.StatusBarWidget.Pin()) { if(StatusBarPinned->IsDrawerOpened(StatusBarDrawerIds::ContentBrowser)) { TSharedPtr ParentTab = StatusBarPinned->GetParentTab(); if (ParentTab && ParentTab->IsForeground() && ParentTab->GetParentWindow() == ParentWindow) { if (EnumHasAllFlags(DrawerTriggerMode, EDrawerTriggerMode::Dismiss)) { StatusBarPinned->DismissDrawer(nullptr); } bWasAlreadyOpened = true; } } } } if(!bWasAlreadyOpened && EnumHasAllFlags(DrawerTriggerMode, EDrawerTriggerMode::Open)) { TSharedPtr Window = ParentWindow; GEditor->GetTimerManager()->SetTimerForNextTick(FTimerDelegate::CreateUObject(this, &UStatusBarSubsystem::HandleDeferredOpenContentBrowser, Window)); } return true; } TSharedRef UStatusBarSubsystem::MakeStatusBarWidget(FName StatusBarName, const TSharedRef& InParentTab, UE::Editor::Toolbars::ECreateStatusBarOptions CreateStatusBarOptions) { using namespace UE::Editor::Toolbars; LLM_SCOPE(ELLMTag::UI); CreateContentBrowserIfNeeded(); TSharedRef StatusBar = SNew(SStatusBar, StatusBarName, InParentTab) .CreateSourceControlSection(!EnumHasAnyFlags(CreateStatusBarOptions, ECreateStatusBarOptions::HideSourceControl)); if (!EnumHasAnyFlags(CreateStatusBarOptions, UE::Editor::Toolbars::ECreateStatusBarOptions::HideContentBrowser)) { FWidgetDrawerConfig ContentBrowserDrawer(StatusBarDrawerIds::ContentBrowser); ContentBrowserDrawer.GetDrawerContentDelegate.BindUObject(this, &UStatusBarSubsystem::OnGetContentBrowser); ContentBrowserDrawer.OnDrawerOpenedDelegate.BindUObject(this, &UStatusBarSubsystem::OnContentBrowserOpened); ContentBrowserDrawer.OnDrawerDismissedDelegate.BindUObject(this, &UStatusBarSubsystem::OnContentBrowserDismissed); ContentBrowserDrawer.ButtonText = LOCTEXT("StatusBar_ContentBrowserButton", "Content Drawer"); ContentBrowserDrawer.ToolTipText = FText::Format(LOCTEXT("StatusBar_ContentBrowserDrawerToolTip", "Opens a temporary content browser above this status which will dismiss when it loses focus ({0})"), FGlobalEditorCommonCommands::Get().OpenContentBrowserDrawer->GetInputText()); ContentBrowserDrawer.Icon = FAppStyle::Get().GetBrush("ContentBrowser.TabIcon"); for (const TUniquePtr& Extension : GlobalStatusBarExtensions) { Extension->ExtendContentBrowserDrawer(ContentBrowserDrawer); } StatusBar->RegisterDrawer(MoveTemp(ContentBrowserDrawer)); } FOutputLogModule& OutputLogModule = FModuleManager::Get().LoadModuleChecked("OutputLog"); TWeakPtr StatusBarWeakPtr = StatusBar; TSharedPtr ConsoleEditBox; if (!EnumHasAnyFlags(CreateStatusBarOptions, UE::Editor::Toolbars::ECreateStatusBarOptions::HideOutputLog)) { FSimpleDelegate OnConsoleClosed = FSimpleDelegate::CreateUObject(this, &UStatusBarSubsystem::OnDebugConsoleClosed, StatusBarWeakPtr); FSimpleDelegate OnConsoleCommandExecuted; TSharedPtr OutputLog; { TSharedRef ConsoleInputBox = OutputLogModule.MakeConsoleInputBox(ConsoleEditBox, OnConsoleClosed, OnConsoleCommandExecuted); auto IsConsoleInputBoxBorderVisible = [ConsoleInputBoxWeak = ConsoleInputBox.ToWeakPtr()]() { if (TSharedPtr ConsoleInputBox = ConsoleInputBoxWeak.Pin()) { FSlateAttributeMetaData::UpdateOnlyVisibilityAttributes(*ConsoleInputBox, FSlateAttributeMetaData::EInvalidationPermission::AllowInvalidationIfConstructed); if (ConsoleInputBox->GetVisibility() == EVisibility::Collapsed) { return EVisibility::Collapsed; } } return EVisibility::Visible; }; OutputLog = SNew(SBorder) .BorderImage(FAppStyle::Get().GetBrush("Brushes.Panel")) .VAlign(VAlign_Center) .Padding(FMargin(6.0f, 0.0f)) .Visibility(MakeAttributeLambda(IsConsoleInputBoxBorderVisible)) [ SNew(SBox) [ ConsoleInputBox ] ]; } FWidgetDrawerConfig OutputLogDrawer(StatusBarDrawerIds::OutputLog); OutputLogDrawer.GetDrawerContentDelegate.BindUObject(this, &UStatusBarSubsystem::OnGetOutputLog); OutputLogDrawer.OnDrawerOpenedDelegate.BindUObject(this, &UStatusBarSubsystem::OnOutputLogOpened); OutputLogDrawer.OnDrawerDismissedDelegate.BindUObject(this, &UStatusBarSubsystem::OnOutputLogDismised); OutputLogDrawer.CustomWidget = OutputLog; OutputLogDrawer.ButtonText = LOCTEXT("StatusBar_OutputLogButton", "Output Log"); OutputLogDrawer.ToolTipText = FText::Format(LOCTEXT("StatusBar_OutputLogButtonTip", "Opens the output log drawer. ({0}) cycles between focusing the console command box, opening the output log drawer, and closing it.\nThe output log drawer may also be toggled directly with ({1})"), FGlobalEditorCommonCommands::Get().OpenConsoleCommandBox->GetInputText(), FGlobalEditorCommonCommands::Get().OpenOutputLogDrawer->GetInputText()); OutputLogDrawer.Icon = FAppStyle::Get().GetBrush("Log.TabIcon"); for (const TUniquePtr& Extension : GlobalStatusBarExtensions) { Extension->ExtendOutputLogDrawer(OutputLogDrawer); } StatusBar->RegisterDrawer(MoveTemp(OutputLogDrawer)); } // Clean up stale status bars for (auto It = StatusBars.CreateIterator(); It; ++It) { if (!It.Value().StatusBarWidget.IsValid()) { It.RemoveCurrent(); } } FStatusBarData StatusBarData; StatusBarData.StatusBarWidget = StatusBar; StatusBarData.ConsoleEditBox = ConsoleEditBox ? ConsoleEditBox : SNullWidget::NullWidget.ToSharedPtr(); StatusBars.Add(StatusBarName, StatusBarData); return StatusBar; } bool UStatusBarSubsystem::ActiveWindowHasStatusBar() const { TSharedPtr ParentWindow = UE::StatusBarSubsystem::Private::FindParentWindow(); if (ParentWindow.IsValid() && ParentWindow->GetType() == EWindowType::Normal) { for (const TPair& StatusBar : StatusBars) { if (TSharedPtr StatusBarPinned = StatusBar.Value.StatusBarWidget.Pin()) { TSharedPtr ParentTab = StatusBarPinned->GetParentTab(); if (ParentTab && ParentTab->IsForeground() && ParentTab->GetParentWindow() == ParentWindow) { return true; } } } } return false; } // This function is identical to UStatusBarSubsystem::ActiveWindowHasStatusBar(), except the variable ParentWindow stores the topmost // Regular window rather than the direct parent window. This function is only used when users click on the hyperlink "Show Output Log" on // the notification window. bool UStatusBarSubsystem::ActiveWindowBehindNotificationHasStatusBar() { TSharedPtr ParentWindow = FSlateApplication::Get().GetActiveTopLevelRegularWindow(); // Same code as ActiveWindowHasStatusBar() from here: if (ParentWindow.IsValid() && ParentWindow->GetType() == EWindowType::Normal) { for (const TPair& StatusBar : StatusBars) { if (TSharedPtr StatusBarPinned = StatusBar.Value.StatusBarWidget.Pin()) { TSharedPtr ParentTab = StatusBarPinned->GetParentTab(); if (ParentTab && ParentTab->IsForeground() && ParentTab->GetParentWindow() == ParentWindow) { return true; } } } } return false; } void UStatusBarSubsystem::RegisterDrawer(FName StatusBarName, FWidgetDrawerConfig&& Drawer, int32 SlotIndex) { if (TSharedPtr StatusBar = GetStatusBar(StatusBarName)) { StatusBar->RegisterDrawer(MoveTemp(Drawer), SlotIndex); } } void UStatusBarSubsystem::UnregisterDrawer(FName StatusBarName, FName DrawerId) { if (TSharedPtr StatusBar = GetStatusBar(StatusBarName)) { StatusBar->UnregisterDrawer(DrawerId); } } FStatusBarMessageHandle UStatusBarSubsystem::PushStatusBarMessage(FName StatusBarName, const TAttribute& InMessage, const TAttribute& InHintText) { if (TSharedPtr StatusBar = GetStatusBar(StatusBarName)) { FStatusBarMessageHandle NewHandle(++MessageHandleCounter); StatusBar->PushMessage(NewHandle, InMessage, InHintText); return NewHandle; } return FStatusBarMessageHandle(); } FStatusBarMessageHandle UStatusBarSubsystem::PushStatusBarMessage(FName StatusBarName, const TAttribute& InMessage) { return PushStatusBarMessage(StatusBarName, InMessage, TAttribute()); } void UStatusBarSubsystem::PopStatusBarMessage(FName StatusBarName, FStatusBarMessageHandle InHandle) { if (TSharedPtr StatusBar = GetStatusBar(StatusBarName)) { StatusBar->PopMessage(InHandle); } } void UStatusBarSubsystem::ClearStatusBarMessages(FName StatusBarName) { if (TSharedPtr StatusBar = GetStatusBar(StatusBarName)) { StatusBar->ClearAllMessages(); } } IGlobalStatusBarExtension& UStatusBarSubsystem::RegisterGlobalStatusBarExtension(TUniquePtr&& Extension) { int32 Index = GlobalStatusBarExtensions.Add(MoveTemp(Extension)); // NOTE: It is safe to return this reference because it's stored in a TUniquePtr which is // guaranteed not to change its address if the array reallocates. return *GlobalStatusBarExtensions[Index]; } TUniquePtr UStatusBarSubsystem::UnregisterGlobalStatusBarExtension(IGlobalStatusBarExtension* Extension) { int32 Index = GlobalStatusBarExtensions.IndexOfByPredicate([Extension](const TUniquePtr& Other) { return &*Other == Extension; }); TUniquePtr RemovedExtension = MoveTemp(GlobalStatusBarExtensions[Index]); GlobalStatusBarExtensions.RemoveAtSwap(Index); return RemovedExtension; } void UStatusBarSubsystem::StartProgressNotification(FProgressNotificationHandle Handle, FText DisplayText, int32 TotalWorkToDo) { // Avoid crashing when starting progress notification while slate is still uninitialized. (i.e. commandlet) if (FSlateApplication::IsInitialized()) { // Get the active window, if one is not active a notification was started when the application was deactivated so use the focus path to find a window or just use the root window if there is no keyboard focus TSharedPtr ActiveWindow = FSlateApplication::Get().GetActiveTopLevelRegularWindow(); if (!ActiveWindow) { TSharedPtr FocusedWidget = FSlateApplication::Get().GetKeyboardFocusedWidget(); ActiveWindow = FocusedWidget ? FSlateApplication::Get().FindWidgetWindow(FocusedWidget.ToSharedRef()) : FGlobalTabmanager::Get()->GetRootWindow(); } // Find the active status bar to display the progress in for (const TPair& StatusBar : StatusBars) { if (TSharedPtr StatusBarPinned = StatusBar.Value.StatusBarWidget.Pin()) { TSharedPtr ParentTab = StatusBarPinned->GetParentTab(); if (ParentTab && ParentTab->IsForeground() && ParentTab->GetParentWindow() == ActiveWindow) { StatusBarPinned->StartProgressNotification(Handle, DisplayText, TotalWorkToDo); break; } } } } } void UStatusBarSubsystem::UpdateProgressNotification(FProgressNotificationHandle Handle, int32 TotalWorkDone, int32 UpdatedTotalWorkToDo, FText UpdatedDisplayText) { for (const TPair& StatusBar : StatusBars) { if (TSharedPtr StatusBarPinned = StatusBar.Value.StatusBarWidget.Pin()) { if (StatusBarPinned->UpdateProgressNotification(Handle, TotalWorkDone, UpdatedTotalWorkToDo, UpdatedDisplayText)) { break; } } } } void UStatusBarSubsystem::CancelProgressNotification(FProgressNotificationHandle Handle) { for (const TPair& StatusBar : StatusBars) { if (TSharedPtr StatusBarPinned = StatusBar.Value.StatusBarWidget.Pin()) { if (StatusBarPinned->CancelProgressNotification(Handle)) { break; } } } } void UStatusBarSubsystem::OnDebugConsoleClosed(TWeakPtr OwningStatusBar) { if (TSharedPtr OwningStatusBarPinned = OwningStatusBar.Pin()) { TSharedPtr OwningWindow = OwningStatusBarPinned->GetParentTab()->GetParentWindow(); ToggleDebugConsole(OwningWindow.ToSharedRef()); } } void UStatusBarSubsystem::CreateContentBrowserIfNeeded() { if(!StatusBarContentBrowser.IsValid()) { IContentBrowserSingleton& ContentBrowserSingleton = FModuleManager::Get().LoadModuleChecked("ContentBrowser").Get();; FContentBrowserConfig Config; Config.bCanSetAsPrimaryBrowser = true; TFunction()> GetTab( [this]() -> TSharedPtr { UE_LOG(LogStatusBar, Log, TEXT("Looking status bar with open content browser drawer...")) for (const TPair& StatusBar : StatusBars) { if (TSharedPtr StatusBarPinned = StatusBar.Value.StatusBarWidget.Pin()) { if (StatusBarPinned->IsDrawerOpened(StatusBarDrawerIds::ContentBrowser)) { UE_LOG(LogStatusBar, Log, TEXT("Using status bar: %s to dock content browser"), *StatusBar.Key.ToString()); return StatusBarPinned->GetParentTab(); } else { UE_LOG(LogStatusBar, Log, TEXT("StatusBar: %s was content browser was not opened"), *StatusBar.Key.ToString()); } } } ensureMsgf(false, TEXT("If we get here somehow a content browser drawer is opened but no status bar claims it")); return TSharedPtr(); } ); StatusBarContentBrowser = ContentBrowserSingleton.CreateContentBrowserDrawer(Config, GetTab); } } void UStatusBarSubsystem::CreateAndShowNewUserTipIfNeeded(TSharedPtr ParentWindow, bool bIsRunningStartupDialog) { if(!bIsRunningStartupDialog) { FString CurrentState = GetNewUserTipState(); if(CurrentState != TEXT("1")) { SNewUserTipNotification::Show(ParentWindow); const FString StoreId = TEXT("Epic Games"); const FString SectionName = TEXT("Unreal Engine/Editor"); const FString KeyName = TEXT("LaunchTipShown"); const FString FallbackIniLocation = TEXT("/Script/UnrealEd.EditorSettings"); const FString FallbackIniKey = TEXT("LaunchTipShownFallback"); // Write that we've shown the notification UStatusBarSubsystem::SetOneTimeStateWithFallback(StoreId, SectionName, KeyName, FallbackIniLocation, FallbackIniKey); } } // Ignore the if the main frame gets recreated this session IMainFrameModule::Get().OnMainFrameCreationFinished().RemoveAll(this); } const FString UStatusBarSubsystem::GetNewUserTipState() const { const FString StoreId = TEXT("Epic Games"); const FString SectionName = TEXT("Unreal Engine/Editor"); const FString KeyName = TEXT("LaunchTipShown"); const FString FallbackIniLocation = TEXT("/Script/UnrealEd.EditorSettings"); const FString FallbackIniKey = TEXT("LaunchTipShownFallback"); FString CurrentState = UStatusBarSubsystem::GetOneTimeStateWithFallback(StoreId, SectionName, KeyName, FallbackIniLocation, FallbackIniKey); return CurrentState; } void UStatusBarSubsystem::CreateAndShowOneTimeIndustryQueryIfNeeded(TSharedPtr ParentWindow, bool bIsRunningStartupDialog) { // Only show for external builds where editor analytics are on and the industry popup is not suppressed if(SOneTimeIndustryQuery::ShouldShowIndustryQuery()) { FString NewUserTipState = GetNewUserTipState(); if (!bIsRunningStartupDialog && NewUserTipState == TEXT("1")) { const FString StoreId = TEXT("Epic Games"); const FString SectionName = TEXT("Unreal Engine/Editor"); const FString KeyName = TEXT("OneTimeIndustryQueryShown"); const FString FallbackIniLocation = TEXT("/Script/UnrealEd.EditorSettings"); const FString FallbackIniKey = TEXT("OneTimeIndustryQueryShownFallback"); FString CurrentState = UStatusBarSubsystem::GetOneTimeStateWithFallback(StoreId, SectionName, KeyName, FallbackIniLocation, FallbackIniKey); if (CurrentState != TEXT("1")) { SOneTimeIndustryQuery::Show(ParentWindow); SetOneTimeStateWithFallback(StoreId, SectionName, KeyName, FallbackIniLocation, FallbackIniKey); } } } // Ignore this if the main frame gets recreated this session IMainFrameModule::Get().OnMainFrameCreationFinished().RemoveAll(this); } const FString UStatusBarSubsystem::GetOneTimeStateWithFallback(const FString StoreId, const FString SectionName, const FString KeyName, const FString FallbackIniLocation, const FString FallbackIniKey) { // Its important that this new user message does not appear after the first launch so we store it in a more permanent place FString CurrentState = TEXT("0"); if (!FPlatformMisc::GetStoredValue(StoreId, SectionName, KeyName, CurrentState)) { // As a fallback where the registry was not readable or writable, save a flag in the editor ini. This will be less permanent as the registry but will prevent // the notification from appearing on every launch GConfig->GetString(*FallbackIniLocation, *FallbackIniKey, CurrentState, GEditorSettingsIni); } return CurrentState; } void UStatusBarSubsystem::SetOneTimeStateWithFallback(const FString StoreId, const FString SectionName, const FString KeyName, const FString FallbackIniLocation, const FString FallbackIniKey) { // Write that we've shown the notification if (!FPlatformMisc::SetStoredValue(StoreId, SectionName, KeyName, TEXT("1"))) { // Use fallback GConfig->SetString(*FallbackIniLocation, *FallbackIniKey, TEXT("1"), GEditorSettingsIni); } } TSharedPtr UStatusBarSubsystem::GetStatusBar(FName StatusBarName) const { return StatusBars.FindRef(StatusBarName).StatusBarWidget.Pin(); } TSharedRef UStatusBarSubsystem::OnGetContentBrowser() { CreateContentBrowserIfNeeded(); return StatusBarContentBrowser.ToSharedRef(); } void UStatusBarSubsystem::OnContentBrowserOpened(FName StatusBarWithDrawerName) { SNewUserTipNotification::Dismiss(); // Dismiss any other content browser that is opened when one status bar opens it. The content browser is a shared resource and shouldn't be in the layout twice for (const TPair& StatusBar : StatusBars) { if (TSharedPtr StatusBarPinned = StatusBar.Value.StatusBarWidget.Pin()) { if (StatusBarWithDrawerName != StatusBarPinned->GetStatusBarName() || StatusBarPinned->IsAnyOtherDrawerOpened(StatusBarDrawerIds::ContentBrowser)) { StatusBarPinned->CloseDrawerImmediately(); } } } IContentBrowserSingleton& ContentBrowserSingleton = FModuleManager::Get().LoadModuleChecked("ContentBrowser").Get();; // Cache off the previously focused widget so we can restore focus if the user hits the focus key again PreviousKeyboardFocusedWidget = FSlateApplication::Get().GetKeyboardFocusedWidget(); ContentBrowserSingleton.FocusContentBrowserSearchField(StatusBarContentBrowser); } void UStatusBarSubsystem::OnContentBrowserDismissed(const TSharedPtr& NewlyFocusedWidget) { if (PreviousKeyboardFocusedWidget.IsValid() && !NewlyFocusedWidget.IsValid()) { FSlateApplication::Get().SetKeyboardFocus(PreviousKeyboardFocusedWidget.Pin()); } IContentBrowserSingleton& ContentBrowserSingleton = FModuleManager::Get().LoadModuleChecked("ContentBrowser").Get();; ContentBrowserSingleton.SaveContentBrowserSettings(StatusBarContentBrowser); PreviousKeyboardFocusedWidget.Reset(); } void UStatusBarSubsystem::HandleDeferredOpenContentBrowser(TSharedPtr ParentWindow) { for (const TPair& StatusBar : StatusBars) { if (TSharedPtr StatusBarPinned = StatusBar.Value.StatusBarWidget.Pin()) { TSharedPtr ParentTab = StatusBarPinned->GetParentTab(); if (ParentTab && ParentTab->IsForeground() && ParentTab->GetParentWindow() == ParentWindow) { StatusBarPinned->OpenDrawer(StatusBarDrawerIds::ContentBrowser); break; } } } } TSharedRef UStatusBarSubsystem::OnGetOutputLog() { FOutputLogModule& OutputLogModule = FModuleManager::Get().LoadModuleChecked("OutputLog"); if (!StatusBarOutputLog) { StatusBarOutputLog = OutputLogModule.MakeOutputLogDrawerWidget(FSimpleDelegate::CreateUObject(this, &UStatusBarSubsystem::OnDebugConsoleDrawerClosed)); } return StatusBarOutputLog.ToSharedRef(); } void UStatusBarSubsystem::OnOutputLogOpened(FName StatusBarWithDrawerName) { // Dismiss any other content browser that is opened when one status bar opens it. The content browser is a shared resource and shouldn't be in the layout twice for (const TPair& StatusBar : StatusBars) { if (TSharedPtr StatusBarPinned = StatusBar.Value.StatusBarWidget.Pin()) { if (StatusBarWithDrawerName != StatusBarPinned->GetStatusBarName() || StatusBarPinned->IsAnyOtherDrawerOpened(StatusBarDrawerIds::OutputLog)) { StatusBarPinned->CloseDrawerImmediately(); } } } FOutputLogModule& OutputLogModule = FModuleManager::Get().LoadModuleChecked("OutputLog"); OutputLogModule.FocusOutputLogConsoleBox(StatusBarOutputLog.ToSharedRef()); } void UStatusBarSubsystem::OnOutputLogDismised(const TSharedPtr& NewlyFocusedWidget) { if (PreviousKeyboardFocusedWidget.IsValid() && !NewlyFocusedWidget.IsValid()) { FSlateApplication::Get().SetKeyboardFocus(PreviousKeyboardFocusedWidget.Pin()); } PreviousKeyboardFocusedWidget.Reset(); } void UStatusBarSubsystem::OnDebugConsoleDrawerClosed() { for (const TPair& StatusBar : StatusBars) { if (TSharedPtr StatusBarPinned = StatusBar.Value.StatusBarWidget.Pin()) { if (StatusBarPinned->IsDrawerOpened(StatusBarDrawerIds::OutputLog)) { ToggleDebugConsole(StatusBarPinned->GetParentTab()->GetParentWindow().ToSharedRef()); break; } } } } #undef LOCTEXT_NAMESPACE