// Copyright Epic Games, Inc. All Rights Reserved. #include "SPackageReportDialog.h" #include "IAssetTools.h" #include "Modules/ModuleManager.h" #include "Widgets/SWindow.h" #include "Layout/WidgetPath.h" #include "SlateOptMacros.h" #include "Framework/Application/SlateApplication.h" #include "Widgets/Images/SImage.h" #include "Widgets/Layout/SUniformGridPanel.h" #include "Widgets/Input/SCheckBox.h" #include "Widgets/Input/SButton.h" #include "Styling/AppStyle.h" #include "Interfaces/IMainFrameModule.h" #include "Interfaces/IPluginManager.h" #define LOCTEXT_NAMESPACE "PackageReportDialog" FPackageReportNode::FPackageReportNode() : CheckedState(ECheckBoxState::Undetermined) , bShouldMigratePackage(nullptr) , Parent(nullptr) {} FPackageReportNode::FPackageReportNode(FString&& InNodeName, FText&& InNodeText, FText&& InToolTipText, bool* bInShouldMigratePackage, FPackageReportNode& Parent) : NodeName(MoveTemp(InNodeName)) , NodeText(MoveTemp(InNodeText)) , ToolTipText(MoveTemp(InToolTipText)) , CheckedState(bInShouldMigratePackage != nullptr ? (*bInShouldMigratePackage ? ECheckBoxState::Checked : ECheckBoxState::Unchecked) : ECheckBoxState::Undetermined) , bShouldMigratePackage(bInShouldMigratePackage) , Parent(&Parent) { } void FPackageReportNode::AddPackage(const FString& PackageName, bool* bInShouldMigratePackage) { TArray PathElements; PackageName.ParseIntoArray(PathElements, TEXT("/"), /*InCullEmpty=*/true); if (PathElements.Num() < 2) { return; } const IAssetTools& AssetTools = IAssetTools::Get(); TSharedPtr Plugin = IPluginManager::Get().FindPlugin(PathElements[0]); bool bFirstElementIsMountPoint = true; if (Plugin.IsValid() && !Plugin->GetVersePath().IsEmpty() && AssetTools.ShowingContentVersePath()) { TArray VersePathElements; Plugin->GetVersePath().ParseIntoArray(VersePathElements, TEXT("/"), /*InCullEmpty=*/true); if (ensure(!VersePathElements.IsEmpty())) { bFirstElementIsMountPoint = false; // Replace the mount point with the Verse path. PathElements.RemoveAt(0); PathElements.Insert(VersePathElements, 0); } } FPackageReportNode* ChildParent = this; for (int32 Index = 0; Index < PathElements.Num(); ++Index) { TSharedPtr* Child = ChildParent->Children.FindByPredicate([&PathElements, Index](const TSharedPtr& Child) { return Child->NodeName == PathElements[Index]; }); if (Child == nullptr) { FString ChildNodeName = MoveTemp(PathElements[Index]); FText ChildNodeText; FText ChildToolTipText; bool* bChildShouldMigratePackage = nullptr; if (bFirstElementIsMountPoint && Index == 0 && Plugin.IsValid()) { ChildNodeText = FText::FromString(Plugin->GetFriendlyName()); ChildToolTipText = FText::FromString(ChildNodeName); } else { ChildNodeText = FText::FromString(ChildNodeName); if (Index + 1 == PathElements.Num()) { ChildToolTipText = FText::FromString(AssetTools.GetUserFacingLongPackageName(FName(PackageName))); bChildShouldMigratePackage = bInShouldMigratePackage; } } Child = &ChildParent->Children.Add_GetRef(MakeShared(MoveTemp(ChildNodeName), MoveTemp(ChildNodeText), MoveTemp(ChildToolTipText), bChildShouldMigratePackage, *ChildParent)); } ChildParent = Child->Get(); } } void FPackageReportNode::ExpandChildrenRecursively(const TSharedRef& Treeview) { for ( auto ChildIt = Children.CreateConstIterator(); ChildIt; ++ChildIt ) { Treeview->SetItemExpansion(*ChildIt, (*ChildIt)->CheckedState != ECheckBoxState::Unchecked); (*ChildIt)->ExpandChildrenRecursively(Treeview); } } void FPackageReportNode::Finalize() { for (const TSharedPtr& Child : Children) { Child->Finalize(); } Children.Sort([](const TSharedPtr& A, const TSharedPtr& B) { return A->NodeText.CompareTo(B->NodeText) < 0; }); UpdateCheckedStateFromChildren(); } void FPackageReportNode::UpdateCheckedStateFromChildren() { if (bShouldMigratePackage == nullptr) { int32 NumChecked = 0; for (const TSharedPtr& Child : Children) { switch (Child->CheckedState) { case ECheckBoxState::Checked: ++NumChecked; break; case ECheckBoxState::Undetermined: CheckedState = ECheckBoxState::Undetermined; return; } } if (NumChecked == Children.Num()) { CheckedState = ECheckBoxState::Checked; } else if (NumChecked == 0) { CheckedState = ECheckBoxState::Unchecked; } else { CheckedState = ECheckBoxState::Undetermined; } } } BEGIN_SLATE_FUNCTION_BUILD_OPTIMIZATION void SPackageReportDialog::Construct( const FArguments& InArgs, const FText& InReportMessage, TArray& InPackageNames, const FOnReportConfirmed& InOnReportConfirmed ) { OnReportConfirmed = InOnReportConfirmed; FolderOpenBrush = FAppStyle::GetBrush("ContentBrowser.AssetTreeFolderOpen"); FolderClosedBrush = FAppStyle::GetBrush("ContentBrowser.AssetTreeFolderClosed"); PackageBrush = FAppStyle::GetBrush("ContentBrowser.ColumnViewAssetIcon"); ConstructNodeTree(InPackageNames); ChildSlot [ SNew(SBorder) .BorderImage( FAppStyle::GetBrush("Docking.Tab.ContentAreaBrush") ) .Padding(FMargin(4, 8, 4, 4)) [ SNew(SVerticalBox) // Report Message +SVerticalBox::Slot() .AutoHeight() .Padding(0, 4) [ SNew(STextBlock) .Text(InReportMessage) .TextStyle( FAppStyle::Get(), "PackageMigration.DialogTitle" ) ] // Tree of packages in the report +SVerticalBox::Slot() .FillHeight(1.f) [ SNew(SBorder) .BorderImage( FAppStyle::GetBrush("ToolPanel.GroupBorder") ) [ SAssignNew( ReportTreeView, PackageReportTree ) .TreeItemsSource(&PackageReportRootNode.Children) .SelectionMode(ESelectionMode::Single) .OnGenerateRow( this, &SPackageReportDialog::GenerateTreeRow ) .OnGetChildren( this, &SPackageReportDialog::GetChildrenForTree ) ] ] // Ok/Cancel buttons +SVerticalBox::Slot() .AutoHeight() .HAlign(HAlign_Right) .Padding(0,4,0,0) [ 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, &SPackageReportDialog::OkClicked) .Text(LOCTEXT("OkButton", "OK")) ] +SUniformGridPanel::Slot(1,0) [ SNew(SButton) .HAlign(HAlign_Center) .ContentPadding( FAppStyle::GetMargin("StandardDialog.ContentPadding") ) .OnClicked(this, &SPackageReportDialog::CancelClicked) .Text(LOCTEXT("CancelButton", "Cancel")) ] ] ] ]; if ( ensure(ReportTreeView.IsValid()) ) { PackageReportRootNode.ExpandChildrenRecursively(ReportTreeView.ToSharedRef()); } } END_SLATE_FUNCTION_BUILD_OPTIMIZATION void SPackageReportDialog::OpenPackageReportDialog(const FText& ReportMessage, TArray& PackageNames, const FOnReportConfirmed& InOnReportConfirmed) { TSharedRef ReportWindow = SNew(SWindow) .Title(LOCTEXT("ReportWindowTitle", "Asset Report")) .ClientSize( FVector2D(600, 500) ) .SupportsMaximize(false) .SupportsMinimize(false) [ SNew(SPackageReportDialog, ReportMessage, PackageNames, InOnReportConfirmed) ]; IMainFrameModule& MainFrameModule = FModuleManager::LoadModuleChecked(TEXT("MainFrame")); if ( MainFrameModule.GetParentWindow().IsValid() ) { FSlateApplication::Get().AddWindowAsNativeChild(ReportWindow, MainFrameModule.GetParentWindow().ToSharedRef()); } else { FSlateApplication::Get().AddWindow(ReportWindow); } } void SPackageReportDialog::CloseDialog() { TSharedPtr Window = FSlateApplication::Get().FindWidgetWindow(AsShared()); if ( Window.IsValid() ) { Window->RequestDestroyWindow(); } } TSharedRef SPackageReportDialog::GenerateTreeRow( TSharedPtr TreeItem, const TSharedRef& OwnerTable) { check(TreeItem.IsValid()); return SNew( STableRow< TSharedPtr >, OwnerTable ) [ // Icon SNew(SHorizontalBox) +SHorizontalBox::Slot() .AutoWidth() [ SNew(SCheckBox) .OnCheckStateChanged(this, &SPackageReportDialog::CheckBoxStateChanged, TreeItem, OwnerTable) .IsChecked(this, &SPackageReportDialog::GetEnabledCheckState, TreeItem) ] +SHorizontalBox::Slot() .AutoWidth() [ SNew(SImage).Image(this, &SPackageReportDialog::GetNodeIcon, TreeItem) ] // Name +SHorizontalBox::Slot() .FillWidth(1.f) [ SNew(STextBlock) .Text(TreeItem->NodeText) .ToolTipText(TreeItem->ToolTipText) .ColorAndOpacity(FSlateColor::UseForeground()) ] ]; } ECheckBoxState SPackageReportDialog::GetEnabledCheckState(TSharedPtr TreeItem) const { return TreeItem.Get()->CheckedState; } void SPackageReportDialog::SetStateRecursive(TSharedPtr TreeItem, bool bIsChecked) { if (TreeItem.Get() == nullptr) { return; } TreeItem.Get()->CheckedState = bIsChecked ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; if (TreeItem.Get()->bShouldMigratePackage) { *(TreeItem.Get()->bShouldMigratePackage) = bIsChecked; } TArray< TSharedPtr > Children; GetChildrenForTree(TreeItem, Children); for (int i = 0; i < Children.Num(); i++) { if (Children[i].Get() == nullptr) { continue; } SetStateRecursive(Children[i], bIsChecked); } } void SPackageReportDialog::CheckBoxStateChanged(ECheckBoxState InCheckBoxState, TSharedPtr TreeItem, TSharedRef OwnerTable) { SetStateRecursive(TreeItem, InCheckBoxState == ECheckBoxState::Checked); FPackageReportNode* CurrentParent = TreeItem->Parent; while (CurrentParent != nullptr) { CurrentParent->UpdateCheckedStateFromChildren(); CurrentParent = CurrentParent->Parent; } OwnerTable.Get().RebuildList(); } void SPackageReportDialog::GetChildrenForTree( TSharedPtr TreeItem, TArray< TSharedPtr >& OutChildren ) { OutChildren = TreeItem->Children; } void SPackageReportDialog::ConstructNodeTree(TArray& PackageNames) { for (ReportPackageData& Package : PackageNames) { PackageReportRootNode.AddPackage(Package.Name, &Package.bShouldMigratePackage); } PackageReportRootNode.Finalize(); } const FSlateBrush* SPackageReportDialog::GetNodeIcon(TSharedPtr ReportNode) const { if ( ReportNode->bShouldMigratePackage != nullptr ) { return PackageBrush; } else if ( ReportTreeView->IsItemExpanded(ReportNode) ) { return FolderOpenBrush; } else { return FolderClosedBrush; } } FReply SPackageReportDialog::OkClicked() { CloseDialog(); OnReportConfirmed.ExecuteIfBound(); return FReply::Handled(); } FReply SPackageReportDialog::CancelClicked() { CloseDialog(); return FReply::Handled(); } #undef LOCTEXT_NAMESPACE