// Copyright Epic Games, Inc. All Rights Reserved. #include "SIntegrationWidget.h" #include "View/SubmitToolStyle.h" #include "View/Widgets/SelectSourceControlUserWidget.h" #include "Widgets/Input/SMultiLineEditableTextBox.h" #include "Widgets/Input/SHyperlink.h" #include "Widgets/Input/SEditableTextBox.h" #include "Widgets/Input/SComboBox.h" #include "Widgets/Input/SCheckBox.h" #include "Widgets/Layout/SSpacer.h" #include "Widgets/Layout/SScrollBox.h" #include "Styling/StyleColors.h" #include "Models/ModelInterface.h" #include "Models/SubmitToolUserPrefs.h" #include "Models/IntegrationOptions.h" #include "SubmitToolUtils.h" void SIntegrationWidget::Construct(const FArguments& InArgs) { ModelInterface = InArgs._ModelInterface; MainWindow = InArgs._MainWindow; ModelInterface->RegisterTagUpdatedCallback(FTagUpdated::FDelegate::CreateLambda([this](const FTag& InTag) { if(ParentWindow.IsValid() && ParentWindow->IsVisible() && InTag.Definition.TagId.Equals(TEXT("#jira"), ESearchCase::IgnoreCase)) { bAreFieldsValid = ModelInterface->ValidateIntegrationOptions(true); } })); TSharedRef ScrollBox = SNew(SScrollBox) .Orientation(EOrientation::Orient_Vertical) + SScrollBox::Slot() .Padding(5, 5) .AutoSize() [ SNew(SVerticalBox) +SVerticalBox::Slot() .Padding(FAppStyle::GetMargin("StandardDialog.SlotPadding").Left, 5) .AutoHeight() [ SNew(STextBlock) .Font(FAppStyle::Get().GetFontStyle("StandardDialog.TitleFont")) .Text(FText::FromString(TEXT("Integration Options"))) ] +SVerticalBox::Slot() .AutoHeight() [ BuildOptions() ] ] +SScrollBox::Slot() .Padding(5, 5) .AutoSize() [ SNew(SHorizontalBox) + SHorizontalBox::Slot() .Padding(FAppStyle::GetMargin("StandardDialog.SlotPadding")) .AutoWidth() [ SNew(SHyperlink) .Style(FSubmitToolStyle::Get(), TEXT("NavigationHyperlink")) .IsEnabled_Lambda([&ModelInterface = ModelInterface]{ return ModelInterface->GetSwarmReview().IsValid(); }) .Text_Lambda([this]() { return FText::FromString(GetSwarmLinkText()); }) .ToolTipText_Lambda([this]() { FString Url; return ModelInterface->GetSwarmReviewUrl(Url) ? FText::FromString(Url) : FText::FromString("No Swarm Review Specified"); }) .OnNavigate_Lambda([this]() { FString Url; if(ModelInterface->GetSwarmReviewUrl(Url)) { FPlatformProcess::LaunchURL(*Url, nullptr, nullptr); }}) ] ] +SScrollBox::Slot() .Padding(5, 5) .AutoSize() [ SNew(SHorizontalBox) + SHorizontalBox::Slot() .Padding(FAppStyle::GetMargin("StandardDialog.SlotPadding")) .AutoWidth() [ SNew(STextBlock) .ColorAndOpacity(FStyleColors::Error) .Visibility_Lambda([&ModelInterface = ModelInterface]{ return ModelInterface->HasSubmitToolTag() ? EVisibility::Collapsed : EVisibility::All; }) .Text(FText::FromString(TEXT("Required validations have failed or need to finish running."))) ] ] +SScrollBox::Slot() .Padding(5, 5) .AutoSize() [ SNew(SHorizontalBox) + SHorizontalBox::Slot() .FillWidth(1.f) [ SNew(SSpacer) ] + SHorizontalBox::Slot() .AutoWidth() .HAlign(HAlign_Right) [ SNew(SButton) .Text(FText::FromString(TEXT("Request Integration"))) .ButtonStyle(FAppStyle::Get(), "PrimaryButton") .IsEnabled_Lambda([this] { return bAreFieldsValid && !ModelInterface->IsBlockingOperationRunning() && ModelInterface->HasSubmitToolTag() && ModelInterface->IsIntegrationRequired(); }) .OnClicked(this, &SIntegrationWidget::OnRequestIntegrationClicked) ] ]; ChildSlot [ SNew(SBorder) .Padding(5,10) .BorderImage(FAppStyle::GetBrush("ToolPanel.GroupBorder")) [ ScrollBox ] ]; } SIntegrationWidget::~SIntegrationWidget() {} void SIntegrationWidget::Open() { if(!ParentWindow.IsValid()) { ParentWindow = SNew(SWindow) .SizingRule(ESizingRule::UserSized) .SupportsMaximize(false) .SupportsMinimize(false) .MinWidth(300) .MinHeight(200); ParentWindow->SetRequestDestroyWindowOverride(FRequestDestroyWindowOverride::CreateLambda([](const TSharedRef& Window) { Window->HideWindow(); })); FSlateApplication::Get().AddWindowAsNativeChild(ParentWindow.ToSharedRef(), MainWindow.ToSharedRef(), false); ParentWindow->SetContent(AsShared()); } ParentWindow->SetTitle(FText::FromString(FString::Printf(TEXT("Integration information for CL %s"), *ModelInterface->GetCLID()))); FDeprecateSlateVector2D NewPosition = MainWindow->GetPositionInScreen(); NewPosition.X += MainWindow->GetSizeInScreen().X; ParentWindow->MoveWindowTo(NewPosition); float ySize = ParentWindow->IsWindowMaximized() ? 300 : MainWindow->GetSizeInScreen().Y - 40; ParentWindow->Resize(FDeprecateSlateVector2D(400, ySize)); FSubmitToolUtils::EnsureWindowIsInView(ParentWindow.ToSharedRef(), true); ParentWindow->BringToFront(); ParentWindow->ShowWindow(); bAreFieldsValid = ModelInterface->ValidateIntegrationOptions(true); } FReply SIntegrationWidget::OnCloseClicked() { this->ParentWindow->HideWindow(); return FReply::Handled(); } void SIntegrationWidget::UpdateUIOptions() { const TMap>& Options = ModelInterface->GetIntegrationOptions(); for(const TPair>& Pair : Options) { if(Pair.Value->FieldDefinition.DependsOn.Num() > 0) { EVisibility Visibility = EVisibility::Collapsed; for(const FString& Dependency : Pair.Value->FieldDefinition.DependsOn) { if(Options.Contains(Dependency)) { FString ActualValue; if(Options[Dependency]->GetJiraValue(ActualValue) || (!Pair.Value->FieldDefinition.DependsOnValue.IsEmpty() && Pair.Value->FieldDefinition.DependsOnValue.Equals(ActualValue))) { Visibility = EVisibility::All; } } } UIOptionsWidget[Pair.Key]->SetVisibility(Pair.Value->FieldDefinition.DependsOn.IsEmpty() ? EVisibility::All : Visibility); } } } FString SIntegrationWidget::GetSwarmLinkText() { FString SwarmLinkText; if(ModelInterface->GetSwarmReview().IsValid()) { SwarmLinkText = TEXT("Swarm Review ") + FString::FromInt(ModelInterface->GetSwarmReview()->Id); } else { SwarmLinkText = TEXT("No Swarm Review Specified"); } return SwarmLinkText; } TSharedRef SIntegrationWidget::BuildOptions() { TSharedRef Parent = SNew(SVerticalBox); TSharedRef Row = SNew(SHorizontalBox); bool bHalfRow = false; for(const TPair>& Pair : ModelInterface->GetIntegrationOptions()) { bool bExpandsTwoColumns; TSharedPtr Widget; switch(Pair.Value->FieldDefinition.Type) { case EFieldType::Bool: bExpandsTwoColumns = false; Widget = CheckboxWithLabel(StaticCastSharedPtr(Pair.Value)); break; case EFieldType::Text: bExpandsTwoColumns = true; Widget = TextWithLabel(StaticCastSharedPtr(Pair.Value)); break; case EFieldType::MultiText: bExpandsTwoColumns = true; Widget = MultiTextWithLabel(StaticCastSharedPtr(Pair.Value)); break; case EFieldType::Combo: bExpandsTwoColumns = true; Widget = ComboWithLabel(StaticCastSharedPtr(Pair.Value)); break; case EFieldType::PerforceUser: bExpandsTwoColumns = true; Widget = PerforceUserSelect(StaticCastSharedPtr(Pair.Value)); break; case EFieldType::UILabel: bExpandsTwoColumns = true; Widget = SNew(SHorizontalBox) +SHorizontalBox::Slot() .FillWidth(1.f) [ SNew(STextBlock) .AutoWrapText(true) .Text(FText::FromString(Pair.Value->FieldDefinition.LabelDisplay)) .TextStyle(FAppStyle::Get(), "BoldText") ]; break; case EFieldType::UISpace: bExpandsTwoColumns = true; Widget = SNew(SHorizontalBox) +SHorizontalBox::Slot() .FillWidth(1.f) .Padding(0,4) [ SNew(SSpacer) ]; break; default: bExpandsTwoColumns = false; Widget = SNullWidget::NullWidget; UE_LOG(LogSubmitTool, Error, TEXT("Invalid type specified for Integration Option")); break; } UIOptionsWidget.Add(Pair.Value->FieldDefinition.Name, Widget.ToSharedRef()); if(bHalfRow && bExpandsTwoColumns) { Parent->AddSlot() .AutoHeight() [ Row ]; Row = SNew(SHorizontalBox); } Row->AddSlot() .FillWidth(1.f) .Padding(FAppStyle::GetMargin("StandardDialog.SlotPadding").Left, 3) .AttachWidget(Widget.ToSharedRef()); if(bExpandsTwoColumns) { Parent->AddSlot() .AutoHeight() [ Row ]; Row = SNew(SHorizontalBox); bHalfRow = false; } else { bHalfRow = !bHalfRow; if(!bHalfRow) { Parent->AddSlot() .AutoHeight() [ Row ]; Row = SNew(SHorizontalBox); bHalfRow = false; } } } UpdateUIOptions(); return Parent; } TSharedRef SIntegrationWidget::CheckboxWithLabel(TSharedPtr InOutOption) { return SNew(SHorizontalBox) +SHorizontalBox::Slot() .AutoWidth() .VAlign(VAlign_Center) [ SNew(SCheckBox) .IsChecked_Lambda([&Field = InOutOption->Value]() { return Field ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; }) .ToolTipText(FText::FromString(InOutOption->FieldDefinition.Tooltip)) .OnCheckStateChanged_Lambda([this, IntegrationOption = InOutOption](ECheckBoxState InNewState) { IntegrationOption->Value = InNewState == ECheckBoxState::Checked; IntegrationValueChanged(IntegrationOption); }) ] +SHorizontalBox::Slot() .AutoWidth() .VAlign(VAlign_Center) [ SNew(SButton) .ButtonStyle(FAppStyle::Get(), "InvisibleButton") .IsFocusable(false) .OnClicked_Lambda([this, InOutOption = InOutOption]() { InOutOption->Value = !InOutOption->Value; IntegrationValueChanged(InOutOption); return FReply::Handled(); }) [ SNew(STextBlock) .ColorAndOpacity_Lambda([InOutOption] { return InOutOption->bInvalid ? FStyleColors::Error : FStyleColors::White; }) .Justification(ETextJustify::Left) .MinDesiredWidth(60) .ToolTipText(FText::FromString(InOutOption->FieldDefinition.Tooltip)) .Text(FText::FromString(InOutOption->FieldDefinition.LabelDisplay)) ] ]; } TSharedRef SIntegrationWidget::TextWithLabel(TSharedPtr InOutOption) { return SNew(SHorizontalBox) +SHorizontalBox::Slot() .AutoWidth() .VAlign(VAlign_Center) [ SNew(STextBlock) .ColorAndOpacity_Lambda([InOutOption] { return InOutOption->bInvalid ? FStyleColors::Error : FStyleColors::White; }) .Justification(ETextJustify::Left) .MinDesiredWidth(60) .ToolTipText(FText::FromString(InOutOption->FieldDefinition.Tooltip)) .Text(FText::FromString(InOutOption->FieldDefinition.LabelDisplay)) ] +SHorizontalBox::Slot() .AutoWidth() .VAlign(VAlign_Center) [ SNew(SEditableTextBox) .Text_Lambda([InOutOption = InOutOption] {return FText::FromString(InOutOption->Value); }) .ToolTipText(FText::FromString(InOutOption->FieldDefinition.Tooltip)) .OnTextChanged_Lambda([this, InOutOption = InOutOption](const FText& InText) { InOutOption->Value = InText.ToString(); IntegrationValueChanged(InOutOption); }) ]; } TSharedRef SIntegrationWidget::MultiTextWithLabel(TSharedPtr InOutOption) { return SNew(SVerticalBox) +SVerticalBox::Slot() .AutoHeight() [ SNew(STextBlock) .ColorAndOpacity_Lambda([InOutOption] { return InOutOption->bInvalid ? FStyleColors::Error : FStyleColors::White; }) .Justification(ETextJustify::Left) .MinDesiredWidth(60) .ToolTipText(FText::FromString(InOutOption->FieldDefinition.Tooltip)) .Text(FText::FromString(InOutOption->FieldDefinition.LabelDisplay)) ] + SVerticalBox::Slot() .AutoHeight() .Padding(0, 3) .VAlign(VAlign_Center) [ SNew(SMultiLineEditableTextBox) .AutoWrapText(true) .ToolTipText(FText::FromString(InOutOption->FieldDefinition.Tooltip)) .Text_Lambda([InOutOption = InOutOption] {return FText::FromString(InOutOption->Value); }) .OnTextChanged_Lambda([this, InOutOption = InOutOption](const FText& InText) { InOutOption->Value = InText.ToString(); IntegrationValueChanged(InOutOption); }) ]; } TSharedRef SIntegrationWidget::ComboWithLabel(TSharedPtr InOutOption) { return SNew(SHorizontalBox) +SHorizontalBox::Slot() .AutoWidth() .Padding(0, 3) .VAlign(VAlign_Center) [ SNew(STextBlock) .ColorAndOpacity_Lambda([InOutOption]{ return InOutOption->bInvalid ? FStyleColors::Error : FStyleColors::White; }) .Justification(ETextJustify::Left) .MinDesiredWidth(60) .ToolTipText(FText::FromString(InOutOption->FieldDefinition.Tooltip)) .Text(FText::FromString(InOutOption->FieldDefinition.LabelDisplay)) ] +SHorizontalBox::Slot() .Padding(FAppStyle::GetMargin("StandardDialog.SlotPadding").Left, 3) .FillWidth(1.f) .VAlign(VAlign_Center) [ SNew(SComboBox>) .ToolTipText(FText::FromString(InOutOption->FieldDefinition.Tooltip)) .OptionsSource(&InOutOption->ComboValues) .OnGenerateWidget_Lambda([](TSharedPtr InString) { return SNew(STextBlock).Text(FText::FromString(*InString)); }) .OnSelectionChanged_Lambda([this, InOutOption = InOutOption](TSharedPtr StringItem, ESelectInfo::Type SelectInfo) { InOutOption->Value = *StringItem; IntegrationValueChanged(InOutOption); }) [ SNew(STextBlock) .Text_Lambda([InOutOption = InOutOption] { return InOutOption->Value.IsEmpty() ? FText::FromString(TEXT("Select...")) : FText::FromString(InOutOption->Value); }) ] ]; } TSharedRef SIntegrationWidget::PerforceUserSelect(TSharedPtr InOutOption) { return SNew(SVerticalBox) +SVerticalBox::Slot() .AutoHeight() [ SNew(STextBlock) .ColorAndOpacity_Lambda([InOutOption] { return InOutOption->bInvalid ? FStyleColors::Error : FStyleColors::White; }) .Justification(ETextJustify::Left) .MinDesiredWidth(60) .ToolTipText(FText::FromString(InOutOption->FieldDefinition.Tooltip)) .Text(FText::FromString(InOutOption->FieldDefinition.LabelDisplay)) ] + SVerticalBox::Slot() .AutoHeight() .Padding(0, 3) .VAlign(VAlign_Center) [ SNew(SHorizontalBox) +SHorizontalBox::Slot() .FillWidth(1.f) .VAlign(VAlign_Center) [ SNew(SEditableTextBox) .Text_Lambda([InOutOption = InOutOption] {return FText::FromString(InOutOption->Value); }) .ToolTipText(FText::FromString(InOutOption->FieldDefinition.Tooltip)) .OnTextChanged_Lambda([this, InOutOption = InOutOption](const FText& InText) { InOutOption->Value = InText.ToString(); IntegrationValueChanged(InOutOption); }) ] +SHorizontalBox::Slot() .Padding(3, 0, 0, 0) .AutoWidth() [ SNew(SBox) .MinDesiredWidth(70) [ SNew(SSelectSourceControlUserWidget) .ButtonText(FText::FromString("Users")) .ModelInterface(ModelInterface) .TargetText(&InOutOption->Value) .TargetName(InOutOption->FieldDefinition.Name) .IsEnabled_Lambda([this]() { return FModelInterface::GetInputEnabled(); }) ] ] ]; } void SIntegrationWidget::IntegrationValueChanged(const TSharedPtr& InOption) { UpdateUIOptions(); if(!InOption->FieldDefinition.ValidationGroups.IsEmpty() || InOption->FieldDefinition.bRequiredValue) { bAreFieldsValid = ModelInterface->ValidateIntegrationOptions(false); } } FReply SIntegrationWidget::OnRequestIntegrationClicked() { ModelInterface->RequestIntegration(); return FReply::Handled(); }