// Copyright Epic Games, Inc. All Rights Reserved. #include "ValidatorsWidget.h" #include "HAL/FileManager.h" #include "Misc/Paths.h" #include "Misc/FileHelper.h" #include "Widgets/Layout/SExpandableArea.h" #include "Widgets/Input/SButton.h" #include "Widgets/Input/SComboBox.h" #include "Widgets/Text/STextBlock.h" #include "Models/SubmitToolUserPrefs.h" #include "Models/JiraIssue.h" #include "Styling/StyleColors.h" #define LOCTEXT_NAMESPACE "ValidatorStatesWidget" namespace ValidatorsColumns { static const FName Type("Type"); static const FName Name("Name"); static const FName Required("Required"); static const FName Status("Status"); static const FName Actions("Actions"); }; void SValidatorsWidget::Construct(const FArguments& InArgs) { Columns = { FValidatorColumn(ValidatorsColumns::Name, 1, true, [](TWeakPtr In) {return In.Pin()->GetValidatorName(); }), FValidatorColumn(ValidatorsColumns::Required, 100, false, [](TWeakPtr In) {return In.Pin()->Definition->IsRequired ? TEXT("Yes") : TEXT("No"); }), FValidatorColumn(ValidatorsColumns::Status, 100, false, [](TWeakPtr In) {return In.Pin()->GetStatusText(); }), FValidatorColumn(ValidatorsColumns::Actions, 300, false, nullptr), }; ModelInterface = InArgs._ModelInterface; OnViewLog = InArgs._OnViewLog; ChildSlot [ BuildValidatorsView() ]; OnFilesRefreshed = ModelInterface->FileRefreshedCallback.AddLambda([this] { RefreshValidatorView(); }); OnPrepareSubmit = FModelInterface::OnStateChanged.AddLambda([this] (const ESubmitToolAppState InFrom, const ESubmitToolAppState InTo) { if (InTo == ESubmitToolAppState::Submitting || InFrom == ESubmitToolAppState::Submitting) { RefreshValidatorView(InTo == ESubmitToolAppState::Submitting); } }); } SValidatorsWidget::~SValidatorsWidget() { ModelInterface->FileRefreshedCallback.Remove(OnFilesRefreshed); FModelInterface::OnStateChanged.Remove(OnPrepareSubmit); } TSharedRef SValidatorsWidget::BuildValidatorsView(bool bListPreSubmitOperations) { Validators.Reset(); TArray> ValidatorsArray = bListPreSubmitOperations ? ModelInterface->GetPreSubmitOperations() : ModelInterface->GetValidators(); const FName ActiveSection = bListPreSubmitOperations ? FName(TEXT("Pre-Submit Operations")) : FName(TEXT("Active Validations")); const FName InactiveSection = bListPreSubmitOperations ? FName(TEXT("Inactive Pre-Submit Operations (Not Applicable to your CL)")) : FName(TEXT("Inactive Validators (Not Applicable to your CL)")); TSharedRef VBox = SNew(SVerticalBox); for(const TWeakPtr& Validator : ValidatorsArray) { FName DisplayName; if (!Validator.Pin()->IsRelevantToCL()) { DisplayName = InactiveSection; } else { DisplayName = ActiveSection; } Validators.FindOrAdd(DisplayName).Add(Validator); } for(const TPair>>& Pair : Validators) { bool bInitiallyCollapsed = false; if (!(bListPreSubmitOperations && Pair.Key.IsEqual(ActiveSection))) { bInitiallyCollapsed = !FSubmitToolUserPrefs::Get()->UISectionExpandState.FindOrAdd(Pair.Key, false); } VBox->AddSlot() .Padding(FMargin(0.f, 2.f)) .AutoHeight() .AttachWidget ( SNew(SExpandableArea) .InitiallyCollapsed(bInitiallyCollapsed) .BorderImage(FAppStyle::Get().GetBrush("Brushes.Header")) .BodyBorderImage(FAppStyle::Get().GetBrush("Brushes.Recessed")) .OnAreaExpansionChanged_Lambda([DisplayName = Pair.Key](bool bExpanded) { FSubmitToolUserPrefs::Get()->UISectionExpandState.FindOrAdd(DisplayName, false) = bExpanded; }) .BorderBackgroundColor_Lambda([&ValidatorArray = Pair.Value, DisplayName = Pair.Key, InactiveSection] { if (DisplayName.IsEqual(InactiveSection)) { return FAppStyle::GetColor("ValidatorStateSuccess"); } bool bRunning = false; bool bValid = true; for(const TWeakPtr& Validator : ValidatorArray) { bRunning |= Validator.Pin()->GetIsRunning(); bValid = bValid && Validator.Pin()->GetHasPassed(); if(Validator.Pin()->GetValidatorState() == EValidationStates::Failed || Validator.Pin()->GetValidatorState() == EValidationStates::Timeout) { return FAppStyle::GetColor("ValidatorStateFail"); } } if(bValid) { return FAppStyle::GetColor("ValidatorStateSuccess"); } if(bRunning) { return FAppStyle::GetColor("ValidatorStateWarning"); } return FAppStyle::GetColor("ValidatorStateNormal");}) .HeaderPadding(FMargin(4.0f, 2.0f)) .Padding(1.0f) .AllowAnimatedTransition(true) .HeaderContent() [ SNew(STextBlock) .Text_Lambda([DisplayName = Pair.Key] {return FText::FromName(DisplayName); }) ] .BodyContent() [ SNew(SBox) .Padding(2.5) [ SAssignNew(ValidatorsListView, SListView>) .SelectionMode(ESelectionMode::None) .ListItemsSource(&Pair.Value) .HeaderRow(ConstructHeadersRow(Pair.Key)) .OnGenerateRow(this, &SValidatorsWidget::GenerateRow) .IsFocusable(false) ] ] ); } return VBox; } void SValidatorsWidget::RefreshValidatorView(bool bListPreSubmitOperations) { ChildSlot.DetachWidget(); ChildSlot.AttachWidget(BuildValidatorsView(bListPreSubmitOperations)); } TSharedRef SValidatorsWidget::ConstructHeadersRow(const FName& GroupName) { TSharedRef HeaderRow = SNew(SHeaderRow); for (size_t i = 0;iAddColumn(args); } return HeaderRow; } class SValidatorNode : public SMultiColumnTableRow> { public: SLATE_BEGIN_ARGS(SValidatorNode) {} SLATE_ARGUMENT(FModelInterface*, ModelInterface) SLATE_ARGUMENT(bool, IsRelevantToCL) SLATE_EVENT(FOnViewValidatorLog, ViewLogCallback) SLATE_END_ARGS() void Construct(const FArguments& InArgs, const TSharedRef& InOwnerTable, TWeakPtr InNode) { ModelInterface = InArgs._ModelInterface; bIsRelevantToCL = InArgs._IsRelevantToCL; Node = InNode; ViewLogCallback = InArgs._ViewLogCallback; Options = TMap, TArray>>(); for(const TPair>& Pair : Node.Pin()->GetValidatorOptions()) { TArray> Values; for (const TPair& Value : Pair.Value) { Values.Add(MakeShared(Value.Key)); } Options.Add(MakeShared(Pair.Key), Values); } SMultiColumnTableRow>::Construct( FSuperRowType::FArguments() .Padding(1.0f), InOwnerTable ); } virtual TSharedRef GenerateWidgetForColumn(const FName& InColumnName) override { TSharedRef box = SNew(SBorder).VAlign(EVerticalAlignment::VAlign_Center); if(InColumnName == ValidatorsColumns::Name) { const FString NameStr = Node.Pin()->Definition->IsRequired ? Node.Pin()->GetValidatorName() : Node.Pin()->GetValidatorName() + TEXT(" - Optional"); TSharedRef Name = SNew(STextBlock) .Text(FText::FromString(NameStr)) .ToolTipText(FText::FromString(Node.Pin()->Definition->ToolTip)); TSharedRef Horizontal = SNew(SHorizontalBox); Horizontal->AddSlot() .VAlign(VAlign_Center) .FillWidth(1.0f) [ Name ]; if(Options.Num() != 0) { for (const TPair, TArray>>& OptionItem : Options) { TSharedRef ComboContent = SNew(STextBlock) .Text_Lambda([this, OptionItem]() { return FText::FromString(Node.Pin()->GetSelectedOptionKey(*OptionItem.Key)); }) .Font(FAppStyle::GetFontStyle("SmallFont")) .ColorAndOpacity_Lambda([this, OptionItem] { if (Node.Pin()->GetOptionType(*OptionItem.Key) == EValidatorOptionType::FilePath) { return IFileManager::Get().FileExists(*Node.Pin()->GetSelectedOptionValue(*OptionItem.Key)) ? FStyleColors::White : FStyleColors::Error; } return FStyleColors::White; }); Horizontal->AddSlot() .AutoWidth() [ SNew(SBox) .MinDesiredWidth(120) [ SNew(SComboBox>) .IsEnabled(OptionItem.Value.Num() > 1) .OptionsSource(&OptionItem.Value) .OnGenerateWidget_Lambda([this, OptionItem](TSharedPtr ChoiceEntry) { return SNew(STextBlock) .Text(FText::FromString(*ChoiceEntry)) .ToolTipText(FText::FromString(Node.Pin()->GetValidatorOptions()[*OptionItem.Key][*ChoiceEntry])) .Font(FAppStyle::GetFontStyle("SmallFont")) .ColorAndOpacity_Lambda([this, OptionItem, ChoiceEntry] { if (Node.Pin()->GetOptionType(*OptionItem.Key) == EValidatorOptionType::FilePath) { return IFileManager::Get().FileExists(*Node.Pin()->GetValidatorOptions()[*OptionItem.Key][*ChoiceEntry]) ? FStyleColors::White : FStyleColors::Error; } return FStyleColors::White; }); }) .OnSelectionChanged_Lambda([this, OptionItem](TSharedPtr NewChoice, ESelectInfo::Type SelectType) { const_cast(Node.Pin().Get())->SetSelectedOption(*OptionItem.Key, *NewChoice); }) [ ComboContent ] ] ]; } box->SetContent(Horizontal); } else { box->SetContent(Name); } } else if (InColumnName == ValidatorsColumns::Required) { TSharedRef Required = SNew(STextBlock).Text(FText::FromString(Node.Pin()->Definition->IsRequired ? TEXT("Required") : TEXT("Optional"))); box->SetContent( Required ); } else if(InColumnName == ValidatorsColumns::Status) { box->SetContent( SNew(STextBlock) .Text_Lambda([this]() { return FText::FromString(Node.Pin()->GetStatusText()); }) .ColorAndOpacity_Lambda([this]{ switch(Node.Pin()->GetValidatorState()) { case EValidationStates::Failed: case EValidationStates::Timeout: return Node.Pin()->Definition->IsRequired ? FAppStyle::GetColor("ValidatorStateFail") : FAppStyle::GetColor("ValidatorStateWarning"); break; case EValidationStates::Valid: case EValidationStates::Skipped: case EValidationStates::Not_Applicable: return FAppStyle::GetColor("ValidatorStateSuccess"); break; case EValidationStates::Running: return FAppStyle::GetColor("ValidatorStateWarning"); break; case EValidationStates::Not_Run: default: return FAppStyle::GetColor("ValidatorStateNormal"); break; }}) ); } else if(InColumnName == ValidatorsColumns::Actions) { box->SetContent( SNew(SHorizontalBox)+SHorizontalBox::Slot() .Padding(3,0,0,0) [ SNew(SButton) .IsEnabled_Lambda([this]() {return ModelInterface->GetInputEnabled() && bIsRelevantToCL; }) .HAlign(EHorizontalAlignment::HAlign_Center) .VAlign(EVerticalAlignment::VAlign_Center) .OnClicked_Lambda([this]() { ModelInterface->ValidateSingle(Node.Pin()->GetValidatorNameId()); return FReply::Handled(); }) [ SNew(STextBlock) .Justification(ETextJustify::Center) .IsEnabled_Lambda([this](){ return !Node.Pin()->GetIsRunningOrQueued();}) .Text_Lambda([this](){ return FText::FromString("Force Run"); }) .Font(FAppStyle::Get().GetFontStyle("SmallFont")) ] ] +SHorizontalBox::Slot() .Padding(3,0,0,0) [ SNew(SButton) .IsEnabled_Lambda([this]() {return ModelInterface->GetInputEnabled() && bIsRelevantToCL; }) .HAlign(EHorizontalAlignment::HAlign_Center) .VAlign(EVerticalAlignment::VAlign_Center) .OnClicked_Lambda([this]() { if(Node.Pin()->GetIsRunningOrQueued()){ ModelInterface->CancelValidations(Node.Pin()->GetValidatorNameId(), true); }else{ ModelInterface->ValidateSingle(Node.Pin()->GetValidatorNameId(), false); } return FReply::Handled(); }) [ SNew(STextBlock) .Justification(ETextJustify::Center) .Text_Lambda([this](){ return Node.Pin()->GetIsRunningOrQueued() ? FText::FromString("Stop") : FText::FromString("Run"); }) .Font(FAppStyle::Get().GetFontStyle("SmallFont")) ] ] +SHorizontalBox::Slot() .Padding(3, 0, 3, 0) [ SNew(SButton) .IsEnabled_Lambda([this]() { return Node.Pin()->GetValidatorState() != EValidationStates::Not_Run && Node.Pin()->GetValidatorState() != EValidationStates::Skipped && Node.Pin()->GetValidatorState() != EValidationStates::Not_Applicable; }) .HAlign(EHorizontalAlignment::HAlign_Center) .VAlign(EVerticalAlignment::VAlign_Center) .OnClicked_Lambda([this]() {ViewLogCallback.ExecuteIfBound(Node.Pin()); return FReply::Handled(); }) [ SNew(STextBlock) .Justification(ETextJustify::Center) .Text(FText::FromString("View Log")) .Font(FAppStyle::Get().GetFontStyle("SmallFont")) ] ]); } return box; } TMap, TArray>> Options; TWeakPtr Node; FModelInterface* ModelInterface; bool bIsRelevantToCL = false; FOnViewValidatorLog ViewLogCallback; }; TSharedRef SValidatorsWidget::GenerateRow(TWeakPtr InItem, const TSharedRef& OwnerTable) const { return SNew(SValidatorNode, OwnerTable, InItem) .ModelInterface(ModelInterface) .IsRelevantToCL(InItem.Pin()->IsRelevantToCL()) .ViewLogCallback(OnViewLog); } void SValidatorsWidget::OnColumnSort(const FName& GroupName, EColumnSortPriority::Type InSortPriority, const FName& InColumnId, EColumnSortMode::Type InSortDirection) { SortMode = InSortDirection; SortByColumn = InColumnId; for (const FValidatorColumn& Column : Columns) { if(!Column.Name.IsEqual(InColumnId)) { continue; } if(Column.SortingFunc == nullptr) { break; } if(InSortDirection == EColumnSortMode::Ascending) { Algo::SortBy(Validators[GroupName], Column.SortingFunc); } else { Algo::Reverse(Validators[GroupName]); } break; } ValidatorsListView->RequestListRefresh(); } EColumnSortMode::Type SValidatorsWidget::GetSortMode(const FName ColumnId) const { if(ColumnId == SortByColumn) { return SortMode; } return EColumnSortMode::None; } #undef LOCTEXT_NAMESPACE