// Copyright Epic Games, Inc. All Rights Reserved. #include "LiveLinkClientPanelToolbar.h" #include "Algo/Accumulate.h" #include "Algo/StableSort.h" #include "AssetRegistry/AssetRegistryModule.h" #include "AssetToolsModule.h" #include "ClassViewerFilter.h" #include "ClassViewerModule.h" #include "ContentBrowserModule.h" #include "Editor.h" #include "EditorFontGlyphs.h" #include "FileHelpers.h" #include "IAssetTools.h" #include "IContentBrowserSingleton.h" #include "ISettingsModule.h" #include "LiveLinkClient.h" #include "LiveLinkEditorPrivate.h" #include "LiveLinkPreset.h" #include "LiveLinkRole.h" #include "LiveLinkSettings.h" #include "LiveLinkSourceFactory.h" #include "Logging/MessageLog.h" #include "LiveLinkSourceSettings.h" #include "Misc/ConfigCacheIni.h" #include "Misc/FileHelper.h" #include "LiveLinkVirtualSubject.h" #include "Misc/MessageDialog.h" #include "Misc/PackageName.h" #include "Misc/Paths.h" #include "ScopedTransaction.h" #include "Framework/Application/SlateApplication.h" #include "Framework/MultiBox/MultiBoxBuilder.h" #include "Framework/Notifications/NotificationManager.h" #include "Styling/SlateIconFinder.h" #include "Widgets/Images/SImage.h" #include "Widgets/Input/SButton.h" #include "Widgets/Input/SEditableTextBox.h" #include "Widgets/Input/STextEntryPopup.h" #include "Widgets/Layout/SSpacer.h" #include "Widgets/Layout/SUniformGridPanel.h" #include "Widgets/Notifications/SNotificationList.h" #include "SPositiveActionButton.h" #include "Widgets/SNullWidget.h" #include "Widgets/SWindow.h" #define LOCTEXT_NAMESPACE "LiveLinkClientPanel" /** Dialog to create a new virtual subject */ class SVirtualSubjectCreateDialog : public SCompoundWidget { public: SLATE_BEGIN_ARGS(SVirtualSubjectCreateDialog) {} /** Pointer to the LiveLinkClient instance. */ SLATE_ARGUMENT(FLiveLinkClient*, LiveLinkClient) SLATE_END_ARGS() /** Constructs this widget with InArgs */ void Construct(const FArguments& InArgs) { static const FName DefaultVirtualSubjectName = TEXT("Virtual"); bOkClicked = false; VirtualSubjectClass = nullptr; LiveLinkClient = InArgs._LiveLinkClient; check(LiveLinkClient); int32 NumVirtualSubjects = Algo::TransformAccumulate(LiveLinkClient->GetSubjects(true, true), [this](const FLiveLinkSubjectKey& SubjectKey) { return LiveLinkClient->IsVirtualSubject(SubjectKey) ? 1 : 0; }, 0); VirtualSubjectName = DefaultVirtualSubjectName; if (NumVirtualSubjects > 0) { VirtualSubjectName = *FString::Printf(TEXT("%s %d"), *DefaultVirtualSubjectName.ToString(), NumVirtualSubjects + 1); } //Default VirtualSubject Source should always exist TArray Sources = LiveLinkClient->GetVirtualSources(); check(Sources.Num() > 0); VirtualSourceGuid = Sources[0]; TSharedPtr TextEntry; SAssignNew(TextEntry, STextEntryPopup) .Label(LOCTEXT("AddVirtualSubjectName", "New Virtual Subject Name")) .DefaultText(FText::FromName(VirtualSubjectName)) .OnTextChanged(this, &SVirtualSubjectCreateDialog::HandleAddVirtualSubjectChanged); VirtualSubjectTextWidget = TextEntry; ChildSlot [ SNew(SBorder) .BorderImage(FAppStyle::GetBrush("Menu.Background")) [ SNew(SBox) [ SNew(SVerticalBox) //For now, it's not possible to create VirtualSubject Sources from the UI. Also, VirtualSubjects created from the UI will be parented under the default VirtualSubject Source //Code is present in case it's required in the future. /*+ SVerticalBox::Slot() .HAlign(HAlign_Fill) .AutoHeight() [ SNew(SHorizontalBox) + SHorizontalBox::Slot() .AutoWidth() .HAlign(HAlign_Left) .Padding(FMargin(0.0f, 0.0f, 5.0f, 0.0f)) [ SNew(STextBlock) .Text(LOCTEXT("SourceNameLabel", "Parent Source:")) ] + SHorizontalBox::Slot() .AutoWidth() .HAlign(HAlign_Left) .Padding(FMargin(5.0f, 0.0f, 5.0f, 0.0f)) [ SNew(STextBlock) .Text(this, &SVirtualSubjectCreateDialog::GetParentSourceText) ] + SHorizontalBox::Slot() .AutoWidth() .HAlign(HAlign_Left) .Padding(FMargin(5.0f, 0.0f, 0.0f, 0.0f)) [ SNew(SComboButton) .OnGetMenuContent(this, &SVirtualSubjectCreateDialog::HandleSourceSelectionComboButton) .ContentPadding(FMargin(4.0, 2.0)) ] ]*/ + SVerticalBox::Slot() .HAlign(HAlign_Fill) .AutoHeight() [ TextEntry->AsShared() ] + SVerticalBox::Slot() .FillHeight(1.0) [ SNew(SBorder) .BorderImage(FAppStyle::GetBrush("ToolPanel.GroupBorder")) .Content() [ SAssignNew(RoleClassPicker, SVerticalBox) ] ] // Ok/Cancel buttons + SVerticalBox::Slot() .AutoHeight() .HAlign(HAlign_Right) .VAlign(VAlign_Bottom) .Padding(8) [ SNew(SUniformGridPanel) .SlotPadding(FAppStyle::GetMargin("StandardDialog.SlotPadding")) .MinDesiredSlotWidth(FAppStyle::GetFloat("StandardDialog.MinDesiredSlotWidth")) .MinDesiredSlotHeight(FAppStyle::GetFloat("StandardDialog.MinDesiredSlotHeight")) + SUniformGridPanel::Slot(0, 0) [ SNew(SButton) .HAlign(HAlign_Center) .ContentPadding(FAppStyle::GetMargin("StandardDialog.ContentPadding")) .OnClicked(this, &SVirtualSubjectCreateDialog::OkClicked) .Text(LOCTEXT("AddVirtualSubjectAdd", "Add")) .IsEnabled(this, &SVirtualSubjectCreateDialog::IsVirtualSubjectClassSelected) ] + SUniformGridPanel::Slot(1, 0) [ SNew(SButton) .HAlign(HAlign_Center) .ContentPadding(FAppStyle::GetMargin("StandardDialog.ContentPadding")) .OnClicked(this, &SVirtualSubjectCreateDialog::CancelClicked) .Text(LOCTEXT("AddVirtualSubjectCancel", "Cancel")) ] ] ] ] ]; MakeRoleClassPicker(); } bool IsVirtualSubjectClassSelected() const { return VirtualSubjectClass != nullptr; } bool ConfigureVirtualSubject() { TSharedRef Window = SNew(SWindow) .Title(LOCTEXT("CreateVirtualSubjectCreation", "Create Virtual Subject")) .ClientSize(FVector2D(400, 300)) .SupportsMinimize(false) .SupportsMaximize(false) [ AsShared() ]; PickerWindow = Window; GEditor->EditorAddModalWindow(Window); return bOkClicked; } private: class FLiveLinkRoleClassFilter : public IClassViewerFilter { public: FLiveLinkRoleClassFilter() = default; virtual bool IsClassAllowed(const FClassViewerInitializationOptions& InInitOptions, const UClass* InClass, TSharedRef< FClassViewerFilterFuncs > InFilterFuncs) override { if (InClass->IsChildOf(ULiveLinkVirtualSubject::StaticClass())) { return InClass->GetDefaultObject()->GetRole() != nullptr && !InClass->HasAnyClassFlags(CLASS_Abstract | CLASS_HideDropDown | CLASS_Deprecated); } return false; } virtual bool IsUnloadedClassAllowed(const FClassViewerInitializationOptions& InInitOptions, const TSharedRef< const IUnloadedBlueprintData > InUnloadedClassData, TSharedRef< FClassViewerFilterFuncs > InFilterFuncs) override { return InUnloadedClassData->IsChildOf(ULiveLinkVirtualSubject::StaticClass()); } }; TSharedRef HandleSourceSelectionComboButton() { // Generate menu FMenuBuilder MenuBuilder(true, nullptr); MenuBuilder.BeginSection("AvailableVirtualSources", LOCTEXT("AvailableSources", "VirtualSubject Sources")); { if (LiveLinkClient) { for (const FGuid& SourceGuid : LiveLinkClient->GetVirtualSources()) { //Always add a None entry MenuBuilder.AddMenuEntry( LiveLinkClient->GetSourceType(SourceGuid), FText::FromName(NAME_None), FSlateIcon(), FUIAction( FExecuteAction::CreateSP(this, &SVirtualSubjectCreateDialog::HandleVirtualSourceSelection, SourceGuid), FCanExecuteAction(), FIsActionChecked::CreateSP(this, &SVirtualSubjectCreateDialog::IsVirtualSourceSelected, SourceGuid) ), NAME_None, EUserInterfaceActionType::RadioButton ); } } else { MenuBuilder.AddWidget(SNullWidget::NullWidget, LOCTEXT("InvalidLiveLink", "Invalid Live Link Client"), false, false); } MenuBuilder.EndSection(); return MenuBuilder.MakeWidget(); } } void HandleVirtualSourceSelection(FGuid InSourceGuid) { VirtualSourceGuid = InSourceGuid; } bool IsVirtualSourceSelected(FGuid InSourceGuid) const { return VirtualSourceGuid == InSourceGuid; } FText GetParentSourceText() const { if (LiveLinkClient != nullptr && VirtualSourceGuid.IsValid()) { return LiveLinkClient->GetSourceType(VirtualSourceGuid); } else { return LOCTEXT("InvalidParentSource", "Invalid Source"); } } /** Creates the combo menu for the role class */ void MakeRoleClassPicker() { // Load the classviewer module to display a class picker FClassViewerModule& ClassViewerModule = FModuleManager::LoadModuleChecked("ClassViewer"); // Fill in options FClassViewerInitializationOptions Options; Options.Mode = EClassViewerMode::ClassPicker; Options.ClassFilters.Add(MakeShared()); RoleClassPicker->ClearChildren(); RoleClassPicker->AddSlot() .AutoHeight() [ SNew(STextBlock) .Text(LOCTEXT("VirtualSubjectRole", "Virtual Subject Role:")) .ShadowOffset(FVector2D(1.0f, 1.0f)) ]; RoleClassPicker->AddSlot() [ ClassViewerModule.CreateClassViewer(Options, FOnClassPicked::CreateSP(this, &SVirtualSubjectCreateDialog::OnClassPicked)) ]; } /** Handler for when a parent class is selected */ void OnClassPicked(UClass* ChosenClass) { VirtualSubjectClass = ChosenClass; } /** Handler for when ok is clicked */ FReply OkClicked() { if (LiveLinkClient) { const FLiveLinkSubjectKey NewVirtualSubjectKey(VirtualSourceGuid, VirtualSubjectName); LiveLinkClient->AddVirtualSubject(NewVirtualSubjectKey, VirtualSubjectClass); } CloseDialog(true); return FReply::Handled(); } void CloseDialog(bool bWasPicked = false) { bOkClicked = bWasPicked; if (PickerWindow.IsValid()) { PickerWindow.Pin()->RequestDestroyWindow(); } } /** Handler for when cancel is clicked */ FReply CancelClicked() { CloseDialog(); return FReply::Handled(); } FReply OnKeyDown(const FGeometry& MyGeometry, const FKeyEvent& InKeyEvent) { if (InKeyEvent.GetKey() == EKeys::Escape) { CloseDialog(); return FReply::Handled(); } return SWidget::OnKeyDown(MyGeometry, InKeyEvent); } void HandleAddVirtualSubjectChanged(const FText& NewSubjectName) { TSharedPtr VirtualSubjectTextWidgetPin = VirtualSubjectTextWidget.Pin(); if (VirtualSubjectTextWidgetPin.IsValid()) { TArray SubjectKey = LiveLinkClient->GetSubjects(true, true); FName SubjectName = *NewSubjectName.ToString(); const FLiveLinkSubjectKey ThisSubjectKey(VirtualSourceGuid, SubjectName); if (SubjectName.IsNone()) { VirtualSubjectTextWidgetPin->SetError(LOCTEXT("VirtualInvalidName", "Invalid Virtual Subject")); } else if (SubjectKey.FindByPredicate([ThisSubjectKey](const FLiveLinkSubjectKey& Key) { return Key == ThisSubjectKey; })) { VirtualSubjectTextWidgetPin->SetError(LOCTEXT("VirtualExistingName", "Subject already exist")); } else { VirtualSubjectName = SubjectName; VirtualSubjectTextWidgetPin->SetError(FText::GetEmpty()); } } } private: FLiveLinkClient* LiveLinkClient; TWeakPtr VirtualSubjectTextWidget; /** A pointer to the window that is asking the user to select a role class */ TWeakPtr PickerWindow; /** The container for the role Class picker */ TSharedPtr RoleClassPicker; /** The virtual subject's class */ TSubclassOf VirtualSubjectClass; /** Selected source guid */ FGuid VirtualSourceGuid; /** The virtual subject's name */ FName VirtualSubjectName; /** True if Ok was clicked */ bool bOkClicked; }; void SLiveLinkClientPanelToolbar::Construct(const FArguments& Args, FLiveLinkClient* InClient) { Client = InClient; ParentWindowOverride = Args._ParentWindow; TArray Results; GetDerivedClasses(ULiveLinkSourceFactory::StaticClass(), Results, true); for (UClass* SourceFactory : Results) { if (!SourceFactory->HasAllClassFlags(CLASS_Abstract | CLASS_Deprecated | CLASS_NewerVersionExists) && CastChecked(SourceFactory->GetDefaultObject())->IsEnabled()) { Factories.Add(NewObject(GetTransientPackage(), SourceFactory)); } } Algo::StableSort(Factories, [](ULiveLinkSourceFactory* LHS, ULiveLinkSourceFactory* RHS) { return LHS->GetSourceDisplayName().CompareTo(RHS->GetSourceDisplayName()) <= 0; }); const int32 ButtonBoxSize = 28; FMargin ButtonPadding(4.f); ChildSlot [ SNew(SBorder) .BorderImage(FAppStyle::GetBrush("ToolPanel.GroupBorder")) .Padding(4.0f) [ SNew(SHorizontalBox) + SHorizontalBox::Slot() .Padding(.0f) .HAlign(HAlign_Left) .AutoWidth() [ Args._CustomHeader ? Args._CustomHeader.ToSharedRef() : SNullWidget::NullWidget ] + SHorizontalBox::Slot() .Padding(.0f) .HAlign(Args._SourceButtonAlignment) .FillWidth(1.0f) [ SNew(SHorizontalBox) + SHorizontalBox::Slot() .AutoWidth() .Padding(.0f) [ SNew(SPositiveActionButton) .OnGetMenuContent(this, &SLiveLinkClientPanelToolbar::OnGenerateSourceMenu) .Icon(FAppStyle::Get().GetBrush("Icons.Plus")) .Text(LOCTEXT("AddSource", "Add Source")) .ToolTipText(LOCTEXT("AddSource_ToolTip", "Add a new Live Link source")) ] + SHorizontalBox::Slot() .Padding(8.f, 0.f, 0.f, 0.f) .AutoWidth() [ SNew(SComboButton) .ContentPadding(4.f) .ComboButtonStyle(FLiveLinkEditorPrivate::GetStyleSet(), "ComboButton") .OnGetMenuContent(this, &SLiveLinkClientPanelToolbar::OnPresetGeneratePresetsMenu) .ForegroundColor(FSlateColor::UseForeground()) .Visibility(Args._ShowPresetPicker ? EVisibility::Visible : EVisibility::Collapsed) .ButtonContent() [ SNew(SHorizontalBox) + SHorizontalBox::Slot() .Padding(4.f, 0.f, 4.f, 0.f) .AutoWidth() [ SNew(SImage) .Image(FSlateIconFinder::FindIconBrushForClass(ULiveLinkPreset::StaticClass())) ] + SHorizontalBox::Slot() [ SNew(STextBlock) .Text(LOCTEXT("PresetsToolbarButton", "Presets")) ] ] ] + SHorizontalBox::Slot() .Padding(8.f, 0.f, 0.f, 0.f) .VAlign(VAlign_Center) .HAlign(HAlign_Center) .AutoWidth() [ SNew(SButton) //.ContentPadding(FMargin(8.f, 0.f, 0.f, 0.f)) .ToolTipText(LOCTEXT("RevertChanges_Text", "Revert all changes made to this take back its original state (either its original preset, or an empty preset).")) .ForegroundColor(FSlateColor::UseForeground()) .ButtonStyle(FAppStyle::Get(), "HoverHintOnly") .OnClicked(this, &SLiveLinkClientPanelToolbar::OnRevertChanges) .Visibility(Args._ShowPresetPicker ? EVisibility::Visible : EVisibility::Collapsed) .IsEnabled(this, &SLiveLinkClientPanelToolbar::HasLoadedLiveLinkPreset) [ SNew(STextBlock) .Font(FAppStyle::Get().GetFontStyle("FontAwesome.11")) .Text(FEditorFontGlyphs::Undo) ] ] + SHorizontalBox::Slot() [ SNew(SSpacer) ] + SHorizontalBox::Slot() .Padding(.0f) .AutoWidth() .HAlign(HAlign_Right) [ SNew(SBox) .WidthOverride(ButtonBoxSize) .Visibility(Args._ShowSettings ? EVisibility::Visible : EVisibility::Collapsed) .HeightOverride(ButtonBoxSize) [ SNew(SCheckBox) .Padding(4.f) .ToolTipText(LOCTEXT("ShowUserSettings_Tip", "Show/Hide the general user settings for Live Link")) .Style(FAppStyle::Get(), "ToggleButtonCheckbox") .ForegroundColor(FSlateColor::UseForeground()) .IsChecked_Lambda([]() { return ECheckBoxState::Unchecked; }) .OnCheckStateChanged_Lambda([](ECheckBoxState CheckState){ FModuleManager::LoadModuleChecked("Settings").ShowViewer("Project", "Plugins", "LiveLink"); }) [ SNew(STextBlock) .Font(FAppStyle::Get().GetFontStyle("FontAwesome.14")) .Text(FEditorFontGlyphs::Cogs) ] ] ] ] ] ]; } void SLiveLinkClientPanelToolbar::AddReferencedObjects(FReferenceCollector& Collector) { Collector.AddReferencedObjects(Factories); } TSharedRef SLiveLinkClientPanelToolbar::OnGenerateSourceMenu() { const bool CloseAfterSelection = true; FMenuBuilder MenuBuilder(CloseAfterSelection, NULL); MenuBuilder.BeginSection("SourceSection", LOCTEXT("Sources", "Live Link Sources")); for (int32 FactoryIndex = 0; FactoryIndex < Factories.Num(); ++FactoryIndex) { ULiveLinkSourceFactory* FactoryInstance = Factories[FactoryIndex]; if (FactoryInstance) { ULiveLinkSourceFactory::EMenuType MenuType = FactoryInstance->GetMenuType(); if (MenuType == ULiveLinkSourceFactory::EMenuType::SubPanel) { MenuBuilder.AddSubMenu( FactoryInstance->GetSourceDisplayName(), FactoryInstance->GetSourceTooltip(), FNewMenuDelegate::CreateSP(this, &SLiveLinkClientPanelToolbar::RetrieveFactorySourcePanel, FactoryIndex), false); } else if (MenuType == ULiveLinkSourceFactory::EMenuType::MenuEntry) { MenuBuilder.AddMenuEntry( FactoryInstance->GetSourceDisplayName(), FactoryInstance->GetSourceTooltip(), FSlateIcon(), FUIAction( FExecuteAction::CreateSP(this, &SLiveLinkClientPanelToolbar::ExecuteCreateSource, FactoryIndex) ), NAME_None, EUserInterfaceActionType::Button); } else { MenuBuilder.AddMenuEntry( FactoryInstance->GetSourceDisplayName(), FactoryInstance->GetSourceTooltip(), FSlateIcon(), FUIAction( FExecuteAction(), FCanExecuteAction::CreateLambda([](){ return false; }) ), NAME_None, EUserInterfaceActionType::Button); } } } MenuBuilder.EndSection(); MenuBuilder.BeginSection("VirtualSourceSection", LOCTEXT("VirtualSources", "Live Link VirtualSubject Sources")); //For now, it's not possible to create VirtualSubject Sources from the UI. //Code is present in case it's required in the future. //MenuBuilder.AddSubMenu( // LOCTEXT("AddVirtualSubjectSource", "Add VirtualSubject Source"), // LOCTEXT("AddVirtualSubjectSourceSubMenu_Tooltip", "Adds a new VirtualSubject Source to LiveLink. New Virtual Subjects can be created under a VirtualSubject Source."), // FNewMenuDelegate::CreateRaw(this, &SLiveLinkClientPanelToolbar::PopulateVirtualSubjectSourceCreationMenu) //); MenuBuilder.AddMenuEntry( LOCTEXT("AddVirtualSubject", "Add Virtual Subject"), LOCTEXT("AddVirtualSubject_Tooltip", "Adds a new virtual subject to Live Link. Instead of coming from a source a virtual subject is a combination of 2 or more real subjects"), FSlateIcon(), FUIAction( FExecuteAction::CreateSP(this, &SLiveLinkClientPanelToolbar::AddVirtualSubject) ), NAME_None, EUserInterfaceActionType::Button); MenuBuilder.EndSection(); return MenuBuilder.MakeWidget(); } void SLiveLinkClientPanelToolbar::RetrieveFactorySourcePanel(FMenuBuilder& MenuBuilder, int32 FactoryIndex) { if (Factories.IsValidIndex(FactoryIndex)) { if (ULiveLinkSourceFactory* FactoryInstance = Factories[FactoryIndex]) { TSharedPtr Widget = FactoryInstance->BuildCreationPanel(ULiveLinkSourceFactory::FOnLiveLinkSourceCreated::CreateSP(this, &SLiveLinkClientPanelToolbar::OnSourceCreated, TSubclassOf(FactoryInstance->GetClass()))); if (Widget.IsValid()) { MenuBuilder.AddWidget(Widget.ToSharedRef() , FText() , true); } } } } void SLiveLinkClientPanelToolbar::ExecuteCreateSource(int32 FactoryIndex) { if (Factories.IsValidIndex(FactoryIndex)) { if (ULiveLinkSourceFactory* FactoryInstance = Factories[FactoryIndex]) { OnSourceCreated(FactoryInstance->CreateSource(FString()), FString(), FactoryInstance->GetClass()); } } } void SLiveLinkClientPanelToolbar::OnSourceCreated(TSharedPtr NewSource, FString ConnectionString, TSubclassOf Factory) { if (NewSource.IsValid()) { FGuid NewSourceGuid = Client->AddSource(NewSource); if (NewSourceGuid.IsValid()) { if (ULiveLinkSourceSettings* Settings = Client->GetSourceSettings(NewSourceGuid)) { Settings->ConnectionString = ConnectionString; Settings->Factory = Factory; } } } FSlateApplication::Get().DismissAllMenus(); } void SLiveLinkClientPanelToolbar::PopulateVirtualSubjectSourceCreationMenu(FMenuBuilder& InMenuBuilder) { TSharedRef CreateSourceWidget = SNew(SBox) .WidthOverride(250) [ SNew(SVerticalBox) + SVerticalBox::Slot() .AutoHeight() [ SNew(SHorizontalBox) + SHorizontalBox::Slot() .HAlign(HAlign_Left) .FillWidth(0.5f) [ SNew(STextBlock) .Text(LOCTEXT("VirtualSourceName", "Source Name")) ] + SHorizontalBox::Slot() .HAlign(HAlign_Fill) .FillWidth(0.5f) [ SAssignNew(VirtualSubjectSourceName, SEditableTextBox) .Text(LOCTEXT("VirtualSourceNameEditable", "VirtualSubjectSource")) ] ] + SVerticalBox::Slot() .HAlign(HAlign_Right) .AutoHeight() [ SNew(SButton) .OnClicked(this, &SLiveLinkClientPanelToolbar::OnAddVirtualSubjectSource) [ SNew(STextBlock) .Text(LOCTEXT("Ok", "Ok")) ] ] ]; InMenuBuilder.AddWidget(CreateSourceWidget, FText()); } FReply SLiveLinkClientPanelToolbar::OnAddVirtualSubjectSource() { if (TSharedPtr VirtualSourceNamePtr = VirtualSubjectSourceName.Pin()) { if (Client) { Client->AddVirtualSubjectSource(*VirtualSourceNamePtr->GetText().ToString()); } } FSlateApplication::Get().DismissAllMenus(); return FReply::Handled(); } void SLiveLinkClientPanelToolbar::AddVirtualSubject() { TSharedRef Dialog = SNew(SVirtualSubjectCreateDialog) .LiveLinkClient(Client); Dialog->ConfigureVirtualSubject(); } TSharedRef SLiveLinkClientPanelToolbar::OnPresetGeneratePresetsMenu() { FMenuBuilder MenuBuilder(true, nullptr); IContentBrowserSingleton& ContentBrowser = FModuleManager::LoadModuleChecked("ContentBrowser").Get(); MenuBuilder.AddMenuEntry( LOCTEXT("SaveAsPreset_Text", "Save As Preset"), LOCTEXT("SaveAsPreset_Tip", "Save the current setup as a new preset that can be imported at a later date"), FSlateIcon(FAppStyle::Get().GetStyleSetName(), "AssetEditor.SaveAsset"), FUIAction( FExecuteAction::CreateSP(this, &SLiveLinkClientPanelToolbar::OnSaveAsPreset) ) ); FAssetPickerConfig AssetPickerConfig; { AssetPickerConfig.SelectionMode = ESelectionMode::Single; AssetPickerConfig.InitialAssetViewType = EAssetViewType::Column; AssetPickerConfig.bFocusSearchBoxWhenOpened = true; AssetPickerConfig.bAllowNullSelection = false; AssetPickerConfig.bShowBottomToolbar = true; AssetPickerConfig.bAutohideSearchBar = false; AssetPickerConfig.bAllowDragging = false; AssetPickerConfig.bCanShowClasses = false; AssetPickerConfig.bShowPathInColumnView = true; AssetPickerConfig.bShowTypeInColumnView = false; AssetPickerConfig.bSortByPathInColumnView = false; AssetPickerConfig.AssetShowWarningText = LOCTEXT("NoPresets_Warning", "No Presets Found"); AssetPickerConfig.Filter.ClassPaths.Add(ULiveLinkPreset::StaticClass()->GetClassPathName()); AssetPickerConfig.OnAssetSelected = FOnAssetSelected::CreateSP(this, &SLiveLinkClientPanelToolbar::OnImportPreset); } MenuBuilder.BeginSection(NAME_None, LOCTEXT("ImportPreset_MenuSection", "Import Preset")); { TSharedRef PresetPicker = SNew(SBox) .MinDesiredWidth(400.f) .MinDesiredHeight(400.f) [ ContentBrowser.CreateAssetPicker(AssetPickerConfig) ]; MenuBuilder.AddWidget(PresetPicker, FText(), true, false); } MenuBuilder.EndSection(); return MenuBuilder.MakeWidget(); } static bool OpenSaveDialog(const FString& InDefaultPath, const FString& InNewNameSuggestion, TSharedPtr InParentWindowOverride, FString& OutPackageName) { FSaveAssetDialogConfig SaveAssetDialogConfig; { SaveAssetDialogConfig.DefaultPath = InDefaultPath; SaveAssetDialogConfig.DefaultAssetName = InNewNameSuggestion; SaveAssetDialogConfig.AssetClassNames.Add(ULiveLinkPreset::StaticClass()->GetClassPathName()); SaveAssetDialogConfig.ExistingAssetPolicy = ESaveAssetDialogExistingAssetPolicy::AllowButWarn; SaveAssetDialogConfig.DialogTitleOverride = LOCTEXT("SaveLiveLinkPresetDialogTitle", "Save Live Link Preset"); SaveAssetDialogConfig.WindowOverride = InParentWindowOverride; } FContentBrowserModule& ContentBrowserModule = FModuleManager::LoadModuleChecked("ContentBrowser"); FString SaveObjectPath = ContentBrowserModule.Get().CreateModalSaveAssetDialog(SaveAssetDialogConfig); if (!SaveObjectPath.IsEmpty()) { OutPackageName = FPackageName::ObjectPathToPackageName(SaveObjectPath); return true; } return false; } bool GetSavePresetPackageName(TSharedPtr InParentWindowOverride, FString& OutName) { ULiveLinkUserSettings* ConfigSettings = GetMutableDefault(); FDateTime Today = FDateTime::Now(); TMap FormatArgs; FormatArgs.Add(TEXT("date"), Today.ToString()); // determine default package path const FString DefaultSaveDirectory = FString::Format(*ConfigSettings->GetPresetSaveDir().Path, FormatArgs); FString DialogStartPath; FPackageName::TryConvertFilenameToLongPackageName(DefaultSaveDirectory, DialogStartPath); if (DialogStartPath.IsEmpty()) { DialogStartPath = TEXT("/Game"); } // determine default asset name FString DefaultName = LOCTEXT("NewLiveLinkPreset", "NewLiveLinkPreset").ToString(); FString UniquePackageName; FString UniqueAssetName; FAssetToolsModule& AssetToolsModule = FModuleManager::LoadModuleChecked("AssetTools"); AssetToolsModule.Get().CreateUniqueAssetName(DialogStartPath / DefaultName, TEXT(""), UniquePackageName, UniqueAssetName); FString DialogStartName = FPaths::GetCleanFilename(UniqueAssetName); FString UserPackageName; FString NewPackageName; // get destination for asset bool bFilenameValid = false; while (!bFilenameValid) { if (!OpenSaveDialog(DialogStartPath, DialogStartName, InParentWindowOverride, UserPackageName)) { return false; } NewPackageName = FString::Format(*UserPackageName, FormatArgs); FText OutError; bFilenameValid = FFileHelper::IsFilenameValidForSaving(NewPackageName, OutError); } ConfigSettings->PresetSaveDir.Path = FPackageName::GetLongPackagePath(UserPackageName); ConfigSettings->SaveConfig(); OutName = MoveTemp(NewPackageName); return true; } void SLiveLinkClientPanelToolbar::OnSaveAsPreset() { FString PackageName; if (!GetSavePresetPackageName(ParentWindowOverride, PackageName)) { return; } FScopedTransaction Transaction(LOCTEXT("SaveAsPreset", "Save As Preset")); // Saving into a new package const FString NewAssetName = FPackageName::GetLongPackageAssetName(PackageName); UPackage* NewPackage = CreatePackage(*PackageName); ULiveLinkPreset* NewPreset = NewObject(NewPackage, *NewAssetName, RF_Public | RF_Standalone | RF_Transactional); if (NewPreset) { NewPreset->BuildFromClient(); NewPreset->MarkPackageDirty(); FAssetRegistryModule::AssetCreated(NewPreset); FEditorFileUtils::PromptForCheckoutAndSave({ NewPackage }, false, false); } LiveLinkPreset = NewPreset; } void SLiveLinkClientPanelToolbar::OnImportPreset(const FAssetData& InPreset) { FSlateApplication::Get().DismissAllMenus(); UObject* PresetAssetData = InPreset.GetAsset(); if (!PresetAssetData) { FNotificationInfo Info(LOCTEXT("LoadPresetFailed", "Failed to load preset")); Info.ExpireDuration = 5.0f; Info.Hyperlink = FSimpleDelegate::CreateStatic([]() { FMessageLog("LoadErrors").Open(EMessageSeverity::Info, true); }); Info.HyperlinkText = LOCTEXT("LoadObjectHyperlink", "Show Message Log"); FSlateNotificationManager::Get().AddNotification(Info); return; } ULiveLinkPreset* ImportedPreset = Cast(PresetAssetData); if (ImportedPreset) { FScopedTransaction Transaction(LOCTEXT("ImportPreset_Transaction", "Import Live Link Preset")); ImportedPreset->ApplyToClientLatent(); } LiveLinkPreset = ImportedPreset; } FReply SLiveLinkClientPanelToolbar::OnRevertChanges() { FText WarningMessage(LOCTEXT("Warning_RevertChanges", "Are you sure you want to revert changes? Your current changes will be discarded.")); if (EAppReturnType::No == FMessageDialog::Open(EAppMsgType::YesNo, WarningMessage)) { return FReply::Handled(); } FScopedTransaction Transaction(LOCTEXT("RevertChanges_Transaction", "Revert Changes")); ULiveLinkPreset* CurrentPreset = LiveLinkPreset.Get(); if (CurrentPreset) { CurrentPreset->ApplyToClientLatent(); } return FReply::Handled(); } bool SLiveLinkClientPanelToolbar::HasLoadedLiveLinkPreset() const { return LiveLinkPreset.IsValid(); } #undef LOCTEXT_NAMESPACE