// Copyright Epic Games, Inc. All Rights Reserved. #include "SAdvancedCopyReportDialog.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/SButton.h" #include "Styling/AppStyle.h" #include "Interfaces/IMainFrameModule.h" #include "Widgets/Views/STableRow.h" #include "Widgets/Layout/SSpacer.h" #include "Widgets/Input/SCheckBox.h" #include "AssetToolsModule.h" #include "AssetToolsSettings.h" #include "Misc/PackageName.h" #include "Widgets/Input/SEditableTextBox.h" #include "AssetRegistry/AssetRegistryModule.h" #define LOCTEXT_NAMESPACE "AdvancedCopyReportDialog" static const FName AssetColumnLabel = TEXT("Asset"); static const FName SourceColumnLabel = TEXT("Source"); static const FName RelativeDestinationColumnLabel = TEXT("RelativeDestination"); struct FCompareFAdvancedCopyReportNodeByName { FORCEINLINE bool operator()( TSharedPtr A, TSharedPtr B ) const { return A->Source < B->Source; } }; FAdvancedCopyReportNode::FAdvancedCopyReportNode() { } FAdvancedCopyReportNode::FAdvancedCopyReportNode(const FString& InSource, const FString& InDestination, TSharedPtr> InIncludedSet) : Source(InSource) , Destination(InDestination) , IncludedSet(InIncludedSet) { } void FAdvancedCopyReportNode::AddPackage(const FString& InSource, const FString& InDestination, const FString& DependencyOf) { AddPackage_Recursive(InSource, InDestination, DependencyOf); } void FAdvancedCopyReportNode::ExpandChildrenRecursively(const TSharedRef& TreeView) { ForAllDescendants([TreeView](const TSharedPtr& Node) { TreeView->SetItemExpansion(Node, false); }); } void FAdvancedCopyReportNode::ForAllDescendants(TFunctionRef& /*Node*/)> RecursiveAction) { for (auto ChildIt = Children.CreateIterator(); ChildIt; ++ChildIt) { RecursiveAction(*ChildIt); (*ChildIt)->ForAllDescendants(RecursiveAction); } } bool FAdvancedCopyReportNode::GetWillCopy() const { if (IncludedSet.IsValid()) { return IncludedSet->Contains(Source); } return false; } void FAdvancedCopyReportNode::SetWillCopy(bool bCopy) { if (bCopy) { IncludedSet->Add(Source); } else { IncludedSet->Remove(Source); } } bool FAdvancedCopyReportNode::AddPackage_Recursive(const FString& InSource, const FString& InDestination, const FString& DependencyOf) { TSharedPtr Child; // If this is not a dependency of an asset, add it to the top of the tree if (DependencyOf.IsEmpty()) { int32 ChildIdx = Children.Add(MakeShareable(new FAdvancedCopyReportNode(InSource, InDestination, IncludedSet))); Child = Children[ChildIdx]; Children.Sort(FCompareFAdvancedCopyReportNodeByName()); return true; } else if (Source == DependencyOf) { for (auto ChildIt = Children.CreateConstIterator(); ChildIt; ++ChildIt) { if ((*ChildIt)->Source == InSource) { Child = (*ChildIt); break; } } // If one was not found, create it if (!Child.IsValid()) { int32 ChildIdx = Children.Add(MakeShareable(new FAdvancedCopyReportNode(InSource, InDestination, IncludedSet))); Child = Children[ChildIdx]; Children.Sort(FCompareFAdvancedCopyReportNodeByName()); return true; } } else { bool bFoundDependencyInChild = false; for (auto ChildIt = Children.CreateConstIterator(); ChildIt; ++ChildIt) { bFoundDependencyInChild = ChildIt->Get()->AddPackage_Recursive(InSource, InDestination, DependencyOf); if (bFoundDependencyInChild) { return true; } } return false; } return false; } BEGIN_SLATE_FUNCTION_BUILD_OPTIMIZATION void SAdvancedCopyReportDialog::Construct( const FArguments& InArgs, const FAdvancedCopyParams& InParams, const FString& InDestination, const TArray>& InDestinationMap, const TArray>& DependencyMap, const SAdvancedCopyReportDialog::FOnReportConfirmed& InOnReportConfirmed ) { OnReportConfirmed = InOnReportConfirmed; InitialDestinationMap = InDestinationMap; CloneSet = MakeShared>(); PackageReportRootNode.IncludedSet = CloneSet; CurrentCopyParams = InParams; Destination = InDestination; ConstructNodeTree(InDestinationMap, DependencyMap); TSharedRef< SHeaderRow > HeaderRowWidget = SNew(SHeaderRow); TSharedPtr PackageColumn = MakeShareable(new SAdvancedCopyColumn(AssetColumnLabel)); Columns.Add(PackageColumn->GetColumnID(), PackageColumn); HeaderRowWidget->AddColumn(PackageColumn->ConstructHeaderRowColumn()); TSharedPtr SourceColumn = MakeShareable(new SAdvancedCopyColumn(SourceColumnLabel)); Columns.Add(SourceColumn->GetColumnID(), SourceColumn); HeaderRowWidget->AddColumn(SourceColumn->ConstructHeaderRowColumn()); TSharedPtr DestinationColumn = MakeShareable(new SAdvancedCopyColumn(RelativeDestinationColumnLabel)); Columns.Add(DestinationColumn->GetColumnID(), DestinationColumn); HeaderRowWidget->AddColumn(DestinationColumn->ConstructHeaderRowColumn()); 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(this, &SAdvancedCopyReportDialog::GetHeaderText, FText::FromString(IAssetTools::Get().GetUserFacingLongPackagePath(Destination))) .TextStyle( FAppStyle::Get(), "PackageMigration.DialogTitle" ) .AutoWrapText(true) ] // Tree of packages in the report +SVerticalBox::Slot() .FillHeight(1.f) [ SNew(SBorder) .BorderImage( FAppStyle::GetBrush("ToolPanel.GroupBorder") ) [ SAssignNew( ReportTreeView, SAdvancedCopyReportTree ) .HeaderRow(HeaderRowWidget) .TreeItemsSource(&PackageReportRootNode.Children) .SelectionMode(ESelectionMode::Single) .OnGenerateRow( this, &SAdvancedCopyReportDialog::GenerateTreeRow ) .OnGetChildren( this, &SAdvancedCopyReportDialog::GetChildrenForTree ) .OnSetExpansionRecursive(this, &SAdvancedCopyReportDialog::SetItemExpansionRecursive) ] ] // Options + SVerticalBox::Slot() .AutoHeight() .Padding(0, 4) [ SNew(SCheckBox) .ToolTipText(LOCTEXT("GenerateDependenciesToCopyTooltip", "Toggle whether or not to search for dependencies. Toggling this will rebuild the destination list.")) .Type(ESlateCheckBoxType::CheckBox) .IsChecked(this, &SAdvancedCopyReportDialog::IsGeneratingDependencies) .OnCheckStateChanged(this, &SAdvancedCopyReportDialog::ToggleGeneratingDependencies) .Padding(4.f) [ SNew(STextBlock) .Text(LOCTEXT("GenerateDependenciesToCopy", "Generate Dependencies to Copy")) ] ] // Find... + SVerticalBox::Slot() .AutoHeight() .Padding(0, 4) [ SNew(SEditableTextBox) .HintText(LOCTEXT("FindHintText", "Find...")) .Text_Lambda([this]() { return FText::FromString(FindString); }) .OnTextCommitted_Lambda([this](const FText& NewText, ETextCommit::Type CommitType) { FindString = NewText.ToString(); }) ] // Replace... + SVerticalBox::Slot() .AutoHeight() .Padding(0, 4) [ SNew(SEditableTextBox) .HintText(LOCTEXT("ReplaceHintText", "Replace...")) .Text_Lambda([this]() { return FText::FromString(ReplaceString); }) .OnTextCommitted_Lambda([this](const FText& NewText, ETextCommit::Type CommitType) { ReplaceString = NewText.ToString(); }) ] // 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, &SAdvancedCopyReportDialog::OkClicked) .Text(LOCTEXT("OkButton", "OK")) ] +SUniformGridPanel::Slot(1,0) [ SNew(SButton) .HAlign(HAlign_Center) .ContentPadding( FAppStyle::GetMargin("StandardDialog.ContentPadding") ) .OnClicked(this, &SAdvancedCopyReportDialog::CancelClicked) .Text(LOCTEXT("CancelButton", "Cancel")) ] ] ] ]; // Make sure the initially selected packages begin as part of the set we're definitely cloning. IAssetRegistry& AssetRegistry = FAssetRegistryModule::GetRegistry(); for (FName OriginalName : InParams.GetSelectedPackageOrFolderNames()) { const FString& OriginalNameString = OriginalName.ToString(); if (!FPackageName::DoesPackageExist(OriginalNameString)) { TArray AssetsInFolder; AssetRegistry.GetAssetsByPath(OriginalName, AssetsInFolder, true, false); for (const FAssetData& Asset : AssetsInFolder) { CloneSet->Add(Asset.PackageName.ToString()); } } else { CloneSet->Add(OriginalName.ToString()); } } } END_SLATE_FUNCTION_BUILD_OPTIMIZATION FText SAdvancedCopyReportDialog::GetHeaderText(const FText InReportMessage) const { if (PackageReportRootNode.Children.Num() == 0) { return LOCTEXT("NoValidSources", "You have not selected any valid sources for advanced copying."); } return FText::Format(LOCTEXT("AdvancedCopyDesc", "The following files will be copied to {0} and references to copied files will be fixed up."), InReportMessage); } ECheckBoxState SAdvancedCopyReportDialog::IsGeneratingDependencies() const { return CurrentCopyParams.bShouldCheckForDependencies ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; } void SAdvancedCopyReportDialog::ToggleGeneratingDependencies(ECheckBoxState NewState) { CurrentCopyParams.bShouldCheckForDependencies = NewState == ECheckBoxState::Checked; CloseDialog(); IAssetTools& AssetTools = IAssetTools::Get(); AssetTools.InitAdvancedCopyFromCopyParams(CurrentCopyParams); } void SAdvancedCopyReportDialog::OpenPackageReportDialog(const FAdvancedCopyParams& InParams, const FString& Destination, const TArray>& InDestinationMap, const TArray>& DependencyMap, const SAdvancedCopyReportDialog::FOnReportConfirmed& InOnReportConfirmed) { TSharedRef ReportWindow = SNew(SWindow) .Title(LOCTEXT("AdvancedCopyReportWindowTitle", "Advanced Copy Asset Report")) .ClientSize( FVector2D(800, 600) ) .SupportsMaximize(true) .SupportsMinimize(true) [ SNew(SAdvancedCopyReportDialog, InParams, Destination, InDestinationMap, DependencyMap, InOnReportConfirmed) ]; IMainFrameModule& MainFrameModule = FModuleManager::LoadModuleChecked(TEXT("MainFrame")); if ( MainFrameModule.GetParentWindow().IsValid() ) { FSlateApplication::Get().AddWindowAsNativeChild(ReportWindow, MainFrameModule.GetParentWindow().ToSharedRef()); } else { FSlateApplication::Get().AddWindow(ReportWindow); } } void SAdvancedCopyReportDialog::CloseDialog() { TSharedPtr Window = FSlateApplication::Get().FindWidgetWindow(AsShared()); if ( Window.IsValid() ) { Window->RequestDestroyWindow(); } } TSharedRef SAdvancedCopyReportDialog::GenerateTreeRow( TSharedPtr TreeItem, const TSharedRef& OwnerTable ) { return SNew(SAdvancedCopyTreeRow, ReportTreeView.ToSharedRef(), SharedThis(this)).Item(TreeItem); } void SAdvancedCopyReportDialog::GetChildrenForTree( TSharedPtr TreeItem, TArray< TSharedPtr >& OutChildren ) { OutChildren = TreeItem->Children; } void SAdvancedCopyReportDialog::ConstructNodeTree(const TArray>& DestinationMap, const TArray>& DependencyMap) { for (int32 MapIndex = 0; MapIndex < DestinationMap.Num(); MapIndex++) { TMap SingleDestinationMap = DestinationMap[MapIndex]; TMap SingleDependencyMap = DependencyMap[MapIndex]; for (auto PackageIt = SingleDestinationMap.CreateConstIterator(); PackageIt; ++PackageIt) { FString SourceString = *PackageIt.Key(); const FName* DependencyOfPtr = SingleDependencyMap.Find(FName(*SourceString)); FString DependencyOf = FString(); if (DependencyOfPtr) { DependencyOf = DependencyOfPtr->ToString(); } PackageReportRootNode.AddPackage(SourceString, *PackageIt.Value(), DependencyOf); } } } void SAdvancedCopyReportDialog::SetItemExpansionRecursive(TSharedPtr TreeItem, bool bInExpansionState) { if (TreeItem.IsValid()) { ReportTreeView->SetItemExpansion(TreeItem, bInExpansionState); for (TSharedPtr& ChildModel : TreeItem->Children) { SetItemExpansionRecursive(ChildModel, bInExpansionState); } } } FReply SAdvancedCopyReportDialog::OkClicked() { CloseDialog(); TArray> FilteredDestinationMap = InitialDestinationMap; for (auto& DestinationsMap : FilteredDestinationMap) { for ( TMap< FString, FString >::TIterator It = DestinationsMap.CreateIterator(); It; ++It ) { if (!CloneSet->Contains(It.Key())) { It.RemoveCurrent(); } else { if (!FindString.IsEmpty()) { FString OutPackageRoot, OutPackagePath, OutPackageName; FPackageName::SplitLongPackageName(It.Value(), OutPackageRoot, OutPackagePath, OutPackageName); FString CroppedDestination = It.Value(); const bool bDidRemovePrefix = CroppedDestination.RemoveFromStart(Destination); if (bDidRemovePrefix) { It.Value() = Destination / CroppedDestination.Replace(*FindString, *ReplaceString); } } } } } FilteredDestinationMap.Shrink(); OnReportConfirmed.ExecuteIfBound(CurrentCopyParams, FilteredDestinationMap); return FReply::Handled(); } FReply SAdvancedCopyReportDialog::CancelClicked() { CloseDialog(); return FReply::Handled(); } void SAdvancedCopyTreeRow::Construct(const FArguments& InArgs, const TSharedRef& OutlinerTreeView, TSharedRef AdvancedCopyReport) { Item = InArgs._Item; ReportDialogWeak = AdvancedCopyReport; auto Args = FSuperRowType::FArguments() .Style(&FAppStyle::Get().GetWidgetStyle("SceneOutliner.TableViewRow")); SMultiColumnTableRow>::Construct(Args, OutlinerTreeView); } TSharedRef SAdvancedCopyTreeRow::GenerateWidgetForColumn(const FName& ColumnName) { auto ItemPtr = Item.Pin(); if (!ItemPtr.IsValid()) { return SNullWidget::NullWidget; } // Create the widget for this item TSharedRef NewItemWidget = SNullWidget::NullWidget; auto Column = ReportDialogWeak.Pin()->GetColumns().FindRef(ColumnName); if (Column.IsValid()) { NewItemWidget = Column->ConstructRowWidget(ItemPtr.ToSharedRef(), *this); } if (ColumnName == AssetColumnLabel) { // The first column gets the tree expansion arrow for this row return SNew(SHorizontalBox) + SHorizontalBox::Slot() .AutoWidth() .Padding(6, 0, 0, 0) [ SNew(SExpanderArrow, SharedThis(this)).IndentAmount(12) ] + SHorizontalBox::Slot() .AutoWidth() .Padding(2, 0, 0, 0) [ SNew(SCheckBox) .IsChecked(this, &SAdvancedCopyTreeRow::GetWillCopyCheckedState) .OnCheckStateChanged(this, &SAdvancedCopyTreeRow::ApplyWillCopyCheckedState) ] + SHorizontalBox::Slot() .FillWidth(1.0f) [ NewItemWidget ]; } else { // Other columns just get widget content -- no expansion arrow needed return NewItemWidget; } } ECheckBoxState SAdvancedCopyTreeRow::GetWillCopyCheckedState() const { auto ItemPtr = Item.Pin(); if (ItemPtr.IsValid()) { return ItemPtr->GetWillCopy() ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; } return ECheckBoxState::Unchecked; } void SAdvancedCopyTreeRow::ApplyWillCopyCheckedState(const ECheckBoxState NewCheckState) { TSharedPtr ItemPtr = Item.Pin(); if (ItemPtr.IsValid()) { const bool bWillCopy = NewCheckState == ECheckBoxState::Checked ? true : false; ItemPtr->SetWillCopy(bWillCopy); const bool bRecursive = FSlateApplication::Get().GetModifierKeys().IsShiftDown() ? true : false; if (bRecursive) { ItemPtr->ForAllDescendants([bWillCopy](const TSharedPtr& Node) { Node->SetWillCopy(bWillCopy); }); } } } SHeaderRow::FColumn::FArguments SAdvancedCopyColumn::ConstructHeaderRowColumn() { return SHeaderRow::Column(GetColumnID()) .FillWidth(2.f) [ SNew(STextBlock) .Text(FText::FromString(FName::NameToDisplayString(ColumnName.ToString(), false))) ]; } const TSharedRef< SWidget > SAdvancedCopyColumn::ConstructRowWidget(TSharedPtr TreeItem, const SAdvancedCopyTreeRow& Row) { if (ColumnName == AssetColumnLabel) { return SNew(STextBlock) .Text(FText::FromString(FPaths::GetBaseFilename(TreeItem.Get()->Source))); } else if (ColumnName == SourceColumnLabel) { FString Source; if (FPackageName::DoesPackageExist(TreeItem.Get()->Source)) { Source = IAssetTools::Get().GetUserFacingLongPackageName(FName(TreeItem.Get()->Source)); } else { Source = IAssetTools::Get().GetUserFacingLongPackagePath(TreeItem.Get()->Source); } return SNew(STextBlock) .Text(FText::FromString(MoveTemp(Source))); } else if (ColumnName == RelativeDestinationColumnLabel) { FString CroppedDestination = TreeItem.Get()->Destination; const bool bDidRemovePrefix = CroppedDestination.RemoveFromStart(Row.GetReportDialog()->GetDestination()); if (bDidRemovePrefix) { CroppedDestination = TEXT(".") + CroppedDestination; } return SNew(STextBlock) .Text(FText::FromString(FPaths::GetPath(CroppedDestination))); } return SNullWidget::NullWidget; } #undef LOCTEXT_NAMESPACE