// Copyright Epic Games, Inc. All Rights Reserved. #include "SPackagesDialog.h" #include "AssetRegistry/AssetData.h" #include "AssetToolsModule.h" #include "Framework/Commands/UIAction.h" #include "Framework/MultiBox/MultiBoxBuilder.h" #include "Framework/Views/ITypedTableView.h" #include "HAL/PlatformApplicationMisc.h" #include "IAssetTools.h" #include "IAssetTypeActions.h" #include "Input/Events.h" #include "InputCoreTypes.h" #include "Internationalization/Internationalization.h" #include "Layout/Children.h" #include "Layout/ChildrenBase.h" #include "Layout/Margin.h" #include "Misc/AssertionMacros.h" #include "Misc/AssetRegistryInterface.h" #include "Misc/Paths.h" #include "Modules/ModuleManager.h" #include "SWarningOrErrorBox.h" #include "SlotBase.h" #include "Algo/AnyOf.h" #include "Styling/AppStyle.h" #include "Styling/ISlateStyle.h" #include "Styling/SlateColor.h" #include "Styling/StyleDefaults.h" #include "Textures/SlateIcon.h" #include "Types/SlateEnums.h" #include "UObject/Object.h" #include "UObject/UObjectHash.h" #include "Widgets/Images/SImage.h" #include "Widgets/Input/SButton.h" #include "Widgets/Input/SCheckBox.h" #include "Widgets/Layout/SBorder.h" #include "Widgets/Layout/SBox.h" #include "Widgets/Layout/SSpacer.h" #include "Widgets/SBoxPanel.h" #include "Widgets/SNullWidget.h" #include "Widgets/SWidget.h" #include "Widgets/Text/STextBlock.h" #include "Widgets/Views/SListView.h" class ITableRow; class STableViewBase; struct FGeometry; struct FSlateBrush; #define LOCTEXT_NAMESPACE "SPackagesDialog" namespace SPackagesDialogDefs { const FName ColumnID_CheckBoxLabel( "CheckBox" ); const FName ColumnID_IconLabel( "Icon" ); const FName ColumnID_AssetLabel( "Asset" ); const FName ColumnID_OwnerLabel( "Owner" ); const FName ColumnID_PackageLabel( "Package" ); const FName ColumnID_TypeLabel( "Type" ); const FName ColumnID_CheckedOutByLabel( "CheckedOutBy" ); const float CheckBoxColumnWidth = 38.0f; const float IconColumnWidth = 22.0f; } UObject* FPackageItem::GetPackageObject() const { UObject* FoundObject = nullptr; if ( !FileName.StartsWith(TEXT("/Temp/Untitled")) ) { FoundObject = Package->FindAssetInPackage(); } return FoundObject; } bool FPackageItem::HasMultipleAssets() const { if ( !FileName.StartsWith(TEXT("/Temp/Untitled")) ) { int32 NumAssets = 0; int32 NumDeleted = 0; ForEachObjectWithPackage(Package, [&NumAssets,&NumDeleted](UObject* Obj) { if (Obj->IsAsset() && !UE::AssetRegistry::FFiltering::ShouldSkipAsset(Obj)) { ++NumAssets; if (!IsValid(Obj)) { ++NumDeleted; } } return true; }, false /*bIncludeNestedObjects*/); return (NumAssets - NumDeleted) > 1; } return false; } bool FPackageItem::GetTypeNameAndColor(FText& OutName, FColor& OutColor) const { if(HasMultipleAssets()) { OutColor = FColor::White; OutName = LOCTEXT("MultipleAssets", "Multiple Assets"); return true; } // Resolve the object belonging to the package and cache. if (!Object.IsValid()) { Object = GetPackageObject(); } if (Object.IsValid(/*bEvenIfPendingKill*/true, /*bThreadsafeTest*/false)) { // Load the asset tools module to get access to the class color UObject* ObjectPtr = Object.Get(/*bEvenIfPendingKill*/true); FAssetData AssetData(ObjectPtr); OutName = FText::FromString(AssetData.AssetClassPath.ToString()); if (!IsValidChecked(ObjectPtr) || UPackage::IsEmptyPackage(GetPackage())) { OutColor = FColor::Red; } else { const FAssetToolsModule& AssetToolsModule = FModuleManager::LoadModuleChecked(TEXT("AssetTools")); const TSharedPtr AssetTypeActions = AssetToolsModule.Get().GetAssetTypeActionsForClass(ObjectPtr->GetClass()).Pin(); if ( AssetTypeActions.IsValid() ) { OutColor = AssetTypeActions->GetTypeColor(); } else { OutColor = FColor::White; } } return true; } // if we do not find any package object, consider the package empty, return a red `Empty Package` OutName = LOCTEXT("NoAssets", "Empty Package"); OutColor = FColor( // Copied from ContentBrowserCLR.cpp 127 + FColor::Red.R / 2, // Desaturate the colors a bit (GB colors were too.. much) 127 + FColor::Red.G / 2, 127 + FColor::Red.B / 2, 200); // Opacity return true; } /** * Construct this widget * * @param InArgs The declaration data for this widget */ void SPackagesDialog::Construct(const FArguments& InArgs) { bSortDirty = false; bReadOnly = InArgs._ReadOnly.Get(); bAllowSourceControlConnection = InArgs._AllowSourceControlConnection.Get(); Message = InArgs._Message; Warning = InArgs._Warning; OnSourceControlStateChanged = InArgs._OnSourceControlStateChanged; SortByColumn = SPackagesDialogDefs::ColumnID_AssetLabel; SortMode = EColumnSortMode::Ascending; ButtonsBox = SNew( SHorizontalBox ); if(bAllowSourceControlConnection) { ButtonsBox->AddSlot() .AutoWidth() .Padding( 2 ) [ SNew(SButton) .Text(LOCTEXT("ConnectToSourceControl", "Connect To Revision Control")) .ToolTipText(LOCTEXT("ConnectToSourceControl_Tooltip", "Connect to a revision control system for tracking changes to your content and levels.")) .ContentPadding(FMargin(10, 3)) .HAlign(HAlign_Right) .VAlign(VAlign_Center) .Visibility(this, &SPackagesDialog::GetConnectToSourceControlVisibility) .OnClicked(this, &SPackagesDialog::OnConnectToSourceControlClicked) ]; } TSharedRef< SHeaderRow > HeaderRowWidget = SNew( SHeaderRow ); if (!bReadOnly) { // clang-format off HeaderRowWidget->AddColumn( SHeaderRow::Column( SPackagesDialogDefs::ColumnID_CheckBoxLabel ) [ SNew(SBox) .Padding(FMargin(6,3,6,3)) .HAlign(HAlign_Center) [ SNew(SCheckBox) .IsChecked_Lambda( [this]() { bool bAllChecked = true; bool bAllUnchecked = true; for (const TSharedPtr& Item : Items) { if (Item->GetState() != ECheckBoxState::Checked) { bAllChecked = false; } if (Item->GetState() != ECheckBoxState::Unchecked) { bAllUnchecked = false; } } if (bAllChecked) { return ECheckBoxState::Checked; } else if (bAllUnchecked) { return ECheckBoxState::Unchecked; } else { return ECheckBoxState::Undetermined; } } ) .OnCheckStateChanged_Lambda( [this](ECheckBoxState InNewState) { for (const TSharedPtr& Item : Items) { if (InNewState == ECheckBoxState::Checked) { if (Item->IsDisabled()) { Item->SetState(ECheckBoxState::Undetermined); } else { Item->SetState(ECheckBoxState::Checked); } } else { Item->SetState(ECheckBoxState::Unchecked); } } ItemListView->RequestListRefresh(); } ) ] ] .FixedWidth( SPackagesDialogDefs::CheckBoxColumnWidth ) ); // clang-format on } HeaderRowWidget->AddColumn( SHeaderRow::Column( SPackagesDialogDefs::ColumnID_IconLabel ) [ SNew(SSpacer) ] .SortMode( this, &SPackagesDialog::GetColumnSortMode, SPackagesDialogDefs::ColumnID_IconLabel ) .OnSort( this, &SPackagesDialog::OnColumnSortModeChanged ) .FixedWidth( SPackagesDialogDefs::IconColumnWidth ) ); HeaderRowWidget->AddColumn( SHeaderRow::Column( SPackagesDialogDefs::ColumnID_AssetLabel ) .DefaultLabel( LOCTEXT("AssetColumnLabel", "Asset" ) ) .SortMode( this, &SPackagesDialog::GetColumnSortMode, SPackagesDialogDefs::ColumnID_AssetLabel ) .OnSort( this, &SPackagesDialog::OnColumnSortModeChanged ) .FillWidth( 5.0f ) ); /* HeaderRowWidget->AddColumn( SHeaderRow::Column(SPackagesDialogDefs::ColumnID_OwnerLabel) .DefaultLabel(LOCTEXT("OwnerColumnLabel", "Owner")) .SortMode(this, &SPackagesDialog::GetColumnSortMode, SPackagesDialogDefs::ColumnID_OwnerLabel) .OnSort(this, &SPackagesDialog::OnColumnSortModeChanged) .FillWidth(7.0f) ); */ HeaderRowWidget->AddColumn( SHeaderRow::Column( SPackagesDialogDefs::ColumnID_PackageLabel ) .DefaultLabel( LOCTEXT("FileColumnLabel", "File" ) ) .SortMode( this, &SPackagesDialog::GetColumnSortMode, SPackagesDialogDefs::ColumnID_PackageLabel ) .OnSort( this, &SPackagesDialog::OnColumnSortModeChanged ) .FillWidth( 7.0f ) ); HeaderRowWidget->AddColumn( SHeaderRow::Column( SPackagesDialogDefs::ColumnID_TypeLabel ) .DefaultLabel( LOCTEXT("TypeColumnLabel", "Type" ) ) .SortMode( this, &SPackagesDialog::GetColumnSortMode, SPackagesDialogDefs::ColumnID_TypeLabel ) .OnSort( this, &SPackagesDialog::OnColumnSortModeChanged ) .FillWidth( 2.0f ) ); if (bAllowSourceControlConnection) { HeaderRowWidget->AddColumn( SHeaderRow::Column(SPackagesDialogDefs::ColumnID_CheckedOutByLabel) .DefaultLabel(LOCTEXT("CheckedOutByColumnLabel", "Checked Out By")) .SortMode(this, &SPackagesDialog::GetColumnSortMode, SPackagesDialogDefs::ColumnID_CheckedOutByLabel) .OnSort(this, &SPackagesDialog::OnColumnSortModeChanged) .FillWidth(4.0f) ); } // clang-format off this->ChildSlot [ SNew(SBorder) .BorderImage(FAppStyle::Get().GetBrush("Brushes.Panel")) .Padding(FMargin(16)) [ SNew(SVerticalBox) +SVerticalBox::Slot() .AutoHeight() .Padding(0.0f,0.0f,0.0f,8.0f) [ SNew(STextBlock) .Text(this, &SPackagesDialog::GetMessage) .AutoWrapText(true) ] +SVerticalBox::Slot() .FillHeight(0.8) [ SAssignNew(ItemListView, SListView< TSharedPtr >) .ListItemsSource(&Items) .OnGenerateRow(this, &SPackagesDialog::MakePackageListItemWidget) .OnContextMenuOpening(this, &SPackagesDialog::MakePackageListContextMenu) .HeaderRow( HeaderRowWidget ) .SelectionMode( ESelectionMode::Multi ) ] + SVerticalBox::Slot() .Padding(0, 16.0f, 0, 0) .AutoHeight() [ SNew(SWarningOrErrorBox) .Visibility(this, &SPackagesDialog::GetWarningVisibility) .Message(this, &SPackagesDialog::GetWarning) ] +SVerticalBox::Slot() .AutoHeight() .Padding(0.0f, 16.0f, 0.0f, 0.0f) .HAlign(HAlign_Right) .VAlign(VAlign_Bottom) [ ButtonsBox.ToSharedRef() ] ] ]; // clang-format on } /** * Removes all checkbox items from the dialog */ void SPackagesDialog::RemoveAll() { Items.Reset(); } /** * Adds a new checkbox item to the dialog * * @param Item The item to be added */ void SPackagesDialog::Add(TSharedPtr Item) { FSimpleDelegate RefreshCallback = FSimpleDelegate::CreateSP(this, &SPackagesDialog::RefreshButtons); Item->SetRefreshCallback(RefreshCallback); Items.Add(Item); RequestSort(); } /** * Adds a new button to the dialog * * @param Button The button to be added */ void SPackagesDialog::AddButton(TSharedPtr Button) { Buttons.Add(Button); ButtonsBox->AddSlot() .AutoWidth() .Padding(5, 0) [ SNew(SButton) .ButtonStyle(&FAppStyle::Get(), (Button->GetStyle() == DBS_Primary) ? "PrimaryButton" : "Button") .TextStyle(&FAppStyle::Get(), (Button->GetStyle() == DBS_Primary) ? "PrimaryButtonText" : "ButtonText") .Text(Button->GetName()) .ToolTipText(Button->GetToolTip()) .IsEnabled(Button.Get(), &FPackageButton::IsEnabled) .HAlign(HAlign_Right) .VAlign(VAlign_Center) .OnClicked(Button.Get(), &FPackageButton::OnButtonClicked) ]; } /** * Sets the message of the widget * * @param InMessage The string that the message should be set to */ void SPackagesDialog::SetMessage(const FText& InMessage) { Message = InMessage; } /** * Sets the warning message of the widget * * @param InMessage The string that the warning message should be set to */ void SPackagesDialog::SetWarning(const FText& InWarning) { Warning = InWarning; } /** * Gets the return type of the dialog and it populates the package array results * * @param OutCheckedPackages Will be populated with the checked packages * @param OutUncheckedPackages Will be populated with the unchecked packages * @param OutUndeterminedPackages Will be populated with the undetermined packages * * @return returns the button that was pressed to remove the dialog */ EDialogReturnType SPackagesDialog::GetReturnType(OUT TArray& OutCheckedPackages, OUT TArray& OutUncheckedPackages, OUT TArray& OutUndeterminedPackages) { /** Set the return type to which button was pressed */ EDialogReturnType ReturnType = DRT_None; for( int32 ButtonIndex = 0; ButtonIndex < Buttons.Num(); ++ButtonIndex) { FPackageButton& Button = *Buttons[ButtonIndex]; if(Button.IsClicked()) { ReturnType = Button.GetType(); break; } } /** Populate the results */ if(ReturnType != DRT_Cancel && ReturnType != DRT_None) { for( int32 ItemIndex = 0; ItemIndex < Items.Num(); ++ItemIndex) { FPackageItem& item = *Items[ItemIndex]; if(item.GetState() == ECheckBoxState::Checked) { OutCheckedPackages.Add(item.GetPackage()); } else if(item.GetState() == ECheckBoxState::Unchecked) { OutUncheckedPackages.Add(item.GetPackage()); } else { OutUndeterminedPackages.Add(item.GetPackage()); } } } return ReturnType; } /** * Gets the widget which is to have keyboard focus on activating the dialog * * @return returns the widget */ TSharedPtr< SWidget > SPackagesDialog::GetWidgetToFocusOnActivate() const { // Find the first visible button. That will be our widget to focus FChildren* ButtonBoxChildren = ButtonsBox->GetChildren(); for( int ButtonIndex = 0; ButtonIndex < ButtonBoxChildren->Num(); ++ButtonIndex ) { TSharedPtr ButtonWidget = ButtonBoxChildren->GetChildAt(ButtonIndex); if(ButtonWidget.IsValid() && ButtonWidget->GetVisibility() == EVisibility::Visible) { return ButtonWidget; } } return TSharedPtr< SWidget >(); } /** * Called when the checkbox items have changed state */ void SPackagesDialog::RefreshButtons() { int32 CheckedItems = 0; int32 UncheckedItems = 0; int32 UndeterminedItems = 0; /** Count the number of checkboxes that we have for each state */ for( int32 ItemIndex = 0; ItemIndex < Items.Num(); ++ItemIndex) { FPackageItem& item = *Items[ItemIndex]; if(item.GetState() == ECheckBoxState::Checked) { CheckedItems++; } else if(item.GetState() == ECheckBoxState::Unchecked) { UncheckedItems++; } else { UndeterminedItems++; } } /** Change the button state based on our selection */ for( int32 ButtonIndex = 0; ButtonIndex < Buttons.Num(); ++ButtonIndex) { FPackageButton& Button = *Buttons[ButtonIndex]; if(Button.GetType() == DRT_MakeWritable) { Button.SetDisabled(UndeterminedItems == 0 && CheckedItems == 0); } else if(Button.GetType() == DRT_CheckOut) { Button.SetDisabled(CheckedItems == 0); } else if (Button.GetType() == DRT_Skip) { Button.SetDisabled(CheckedItems > 0); } } } /** * Makes the widget for the checkbox items in the list view */ TSharedRef SPackagesDialog::MakePackageListItemWidget( TSharedPtr Item, const TSharedRef& OwnerTable) { return SNew( SPackageItemsListRow, OwnerTable ) .PackagesDialog( SharedThis( this ) ) .Item( Item ); } TSharedRef SPackagesDialog::GenerateWidgetForItemAndColumn( TSharedPtr Item, const FName ColumnID ) const { check(Item.IsValid()); const FMargin RowPadding(3, 3, 3, 3); TSharedPtr ItemContentWidget; if (ColumnID == SPackagesDialogDefs::ColumnID_CheckBoxLabel) { // clang-format off ItemContentWidget = SNew(SHorizontalBox) +SHorizontalBox::Slot() .Padding(FMargin(10, 3, 6, 3)) [ SNew(SCheckBox) .IsChecked(Item.Get(), &FPackageItem::OnGetDisplayCheckState) .OnCheckStateChanged_Lambda( [this, WeakItem = Item.ToWeakPtr()](ECheckBoxState InNewState) { if (TSharedPtr PinnedItem = WeakItem.Pin()) { const bool bAllIfNone = false; const TArray> SelectedItems = this->GetSelectedItems(bAllIfNone); const bool bItemIsSelected = SelectedItems.Contains(PinnedItem); // If this item is selected, check/uncheck it together with all other checked items. if (bItemIsSelected) { for (const TSharedPtr& SelectedItem : SelectedItems) { SelectedItem->SetState(InNewState); } } // If this item is NOT in the current selection, just check/uncheck the current item. else { PinnedItem->SetState(InNewState); } } } ) ]; // clang-format on } else if (ColumnID == SPackagesDialogDefs::ColumnID_IconLabel) { // Choose the icon based on the severity const FSlateBrush* IconBrush = Item->GetIconName().IsEmpty() ? FStyleDefaults::GetNoBrush() : FAppStyle::GetBrush(*(Item->GetIconName())); ItemContentWidget = SNew(SHorizontalBox) +SHorizontalBox::Slot() .HAlign(HAlign_Center) .VAlign(VAlign_Center) [ SNew(SImage) .Image( IconBrush ) .ToolTipText(FText::FromString(Item->GetToolTip())) .IsEnabled(!Item->IsDisabled()) ]; } else if (ColumnID == SPackagesDialogDefs::ColumnID_AssetLabel) { const FString& AssetDisplayName = Item->GetAssetDisplayName(); ItemContentWidget = SNew(SHorizontalBox) +SHorizontalBox::Slot() .Padding(RowPadding) [ SNew(STextBlock) .Text(FText::FromString(AssetDisplayName)) .IsEnabled(!Item->IsDisabled()) ]; } else if (ColumnID == SPackagesDialogDefs::ColumnID_OwnerLabel) { const FString& OwnerName = Item->GetOwnerName(); ItemContentWidget = SNew(SHorizontalBox) +SHorizontalBox::Slot() .Padding(RowPadding) [ SNew(STextBlock) .Text(FText::FromString(OwnerName)) .ToolTipText(FText::FromString(OwnerName)) .IsEnabled(!Item->IsDisabled()) ]; } else if (ColumnID == SPackagesDialogDefs::ColumnID_PackageLabel) { const FString& PackageName = Item->GetPackageName(); const FString& FileName = Item->GetFileName(); const UE::Core::FVersePath& VersePath = Item->GetVersePath(); // Prefer the Verse path if valid. It will be invalid if we are not showing Verse paths. ItemContentWidget = SNew(SHorizontalBox) +SHorizontalBox::Slot() .Padding(RowPadding) [ SNew(STextBlock) .Text(FText::FromString(VersePath.IsValid() ? VersePath.ToString() : PackageName)) .ToolTipText(FText::FromString(FileName)) .IsEnabled(!Item->IsDisabled()) ]; } else if (ColumnID == SPackagesDialogDefs::ColumnID_TypeLabel) { // Extract the type and color for the package FColor PackageColor; FText PackageType; Item->GetTypeNameAndColor(PackageType, PackageColor); ItemContentWidget = SNew(SHorizontalBox) +SHorizontalBox::Slot() .Padding(RowPadding) [ SNew(STextBlock) .Text(PackageType) .ToolTipText(PackageType) .IsEnabled(!Item->IsDisabled()) .ColorAndOpacity(PackageColor) ]; } else if (ColumnID == SPackagesDialogDefs::ColumnID_CheckedOutByLabel) { check(bAllowSourceControlConnection); FString CheckedOutByString = Item->GetCheckedOutByString(); ItemContentWidget = SNew(SHorizontalBox) + SHorizontalBox::Slot() .Padding(RowPadding) [ SNew(STextBlock) .Text(FText::FromString(CheckedOutByString)) .ToolTipText(FText::FromString(CheckedOutByString)) .IsEnabled(!Item->IsDisabled()) ]; } return ItemContentWidget.ToSharedRef(); } TSharedPtr SPackagesDialog::MakePackageListContextMenu() const { FMenuBuilder MenuBuilder( true, NULL ); const TArray< TSharedPtr > SelectedItems = GetSelectedItems( false ); if( SelectedItems.Num() > 0 ) { MenuBuilder.BeginSection("FilePackage", LOCTEXT("PackageHeading", "Asset")); { MenuBuilder.AddMenuEntry( LOCTEXT("SCCDiffAgainstDepot", "Diff Against Depot"), LOCTEXT("SCCDiffAgainstDepotTooltip", "Look at differences between your version of the asset and that in revision control."), FSlateIcon(), FUIAction( FExecuteAction::CreateSP( this, &SPackagesDialog::ExecuteSCCDiffAgainstDepot ), FCanExecuteAction::CreateSP( this, &SPackagesDialog::CanExecuteSCCDiffAgainstDepot ) ) ); MenuBuilder.AddMenuEntry( LOCTEXT("SCCCopyFilePathToClipboard", "Copy File Path"), LOCTEXT("SCCCopyFilePathToClipboardTooltip", "Copies the file path on disk to the clipboard."), FSlateIcon(), FUIAction( FExecuteAction::CreateLambda( [SelectedItems] () { TArray Paths; Paths.Reserve(SelectedItems.Num()); for (const TSharedPtr& PackageItem: SelectedItems) { if (!PackageItem->GetFileName().IsEmpty()) { Paths.Add(PackageItem->GetFileName()); } } FPlatformApplicationMisc::ClipboardCopy(*FString::Join(Paths, LINE_TERMINATOR)); } ), FCanExecuteAction::CreateLambda( [SelectedItems] () { return Algo::AnyOf(SelectedItems, [](const TSharedPtr& PackageItem) { return !PackageItem->GetFileName().IsEmpty(); }); } ) ) ); MenuBuilder.AddMenuEntry( LOCTEXT("SCCCopyPackagePathToClipboard", "Copy Package Path"), LOCTEXT("SCCCopyPackagePathToClipboardTooltip", "Copies the package path to the clipboard."), FSlateIcon(), FUIAction( FExecuteAction::CreateLambda( [SelectedItems] () { TArray Paths; Paths.Reserve(SelectedItems.Num()); for (const TSharedPtr& PackageItem: SelectedItems) { if (!PackageItem->GetFileName().IsEmpty()) { Paths.Add(PackageItem->GetPackageName()); } } FPlatformApplicationMisc::ClipboardCopy(*FString::Join(Paths, LINE_TERMINATOR)); } ) ) ); const FAssetToolsModule& AssetToolsModule = FAssetToolsModule::GetModule(); if (AssetToolsModule.Get().ShowingContentVersePath()) { MenuBuilder.AddMenuEntry( LOCTEXT("SCCCopyVersePathToClipboard", "Copy Verse Path"), LOCTEXT("SCCCopyVersePathToClipboardTooltip", "Copies the Verse path to the clipboard."), FSlateIcon(), FUIAction( FExecuteAction::CreateLambda([SelectedItems]() { TArray VersePaths; VersePaths.Reserve(SelectedItems.Num()); for (const TSharedPtr& PackageItem: SelectedItems) { if (PackageItem->GetVersePath().IsValid()) { VersePaths.Add(PackageItem->GetVersePath().ToString()); } } FPlatformApplicationMisc::ClipboardCopy(*FString::Join(VersePaths, LINE_TERMINATOR)); } ), FCanExecuteAction::CreateLambda([SelectedItems]() { return Algo::AnyOf(SelectedItems, [](const TSharedPtr& PackageItem) { return PackageItem->GetVersePath().IsValid(); }); }) ) ); } } MenuBuilder.EndSection(); } return MenuBuilder.MakeWidget(); } bool SPackagesDialog::CanExecuteSCCDiffAgainstDepot() const { return ISourceControlModule::Get().IsEnabled() && ISourceControlModule::Get().GetProvider().IsAvailable(); } void SPackagesDialog::ExecuteSCCDiffAgainstDepot() const { const FAssetToolsModule& AssetToolsModule = FModuleManager::LoadModuleChecked(TEXT("AssetTools")); const TArray< TSharedPtr > SelectedItems = GetSelectedItems( false ); for(int32 ItemIdx=0; ItemIdx SelectedItem = SelectedItems[ItemIdx]; check( SelectedItem.IsValid() ); UObject* Object = SelectedItem->GetPackageObject(); if( Object ) { const FString PackagePath = SelectedItem->GetFileName(); const FString PackageName = FPaths::GetBaseFilename( PackagePath ); AssetToolsModule.Get().DiffAgainstDepot( Object, PackagePath, PackageName ); } } } TArray< TSharedPtr > SPackagesDialog::GetSelectedItems( bool bAllIfNone ) const { //get the list of highlighted packages TArray< TSharedPtr > SelectedItems = ItemListView->GetSelectedItems(); if ( SelectedItems.Num() == 0 && bAllIfNone ) { //If no packages are explicitly highlighted, return all packages in the list. SelectedItems = Items; } return SelectedItems; } void SPackagesDialog::Tick(const FGeometry& AllottedGeometry, const double InCurrentTime, const float InDeltaTime) { SCompoundWidget::Tick(AllottedGeometry, InCurrentTime, InDeltaTime); if(bSortDirty) { bSortDirty = false; // Sort the list of root items SortTree(); ItemListView->RequestListRefresh(); } } FReply SPackagesDialog::OnKeyDown( const FGeometry& MyGeometry, const FKeyEvent& InKeyEvent ) { if( InKeyEvent.GetKey() == EKeys::Escape ) { for( int32 ButtonIndex = 0; ButtonIndex < Buttons.Num(); ++ButtonIndex ) { FPackageButton& Button = *Buttons[ ButtonIndex ]; if( Button.GetType() == DRT_Cancel ) { return Button.OnButtonClicked(); } } } return SCompoundWidget::OnKeyDown( MyGeometry, InKeyEvent ); } EVisibility SPackagesDialog::GetConnectToSourceControlVisibility() const { if(bAllowSourceControlConnection) { if(!ISourceControlModule::Get().IsEnabled() || !ISourceControlModule::Get().GetProvider().IsAvailable()) { return EVisibility::Visible; } } return EVisibility::Collapsed; } FReply SPackagesDialog::OnConnectToSourceControlClicked() const { ISourceControlModule::Get().ShowLoginDialog(FSourceControlLoginClosed(), ELoginWindowMode::Modal); OnSourceControlStateChanged.ExecuteIfBound(); return FReply::Handled(); } void SPackagesDialog::PopulateIgnoreForSaveItems( const TSet& InIgnorePackages ) { for( auto ItItem=Items.CreateIterator(); ItItem; ++ItItem ) { const FString& ItemName = (*ItItem)->GetFileName(); const ECheckBoxState CheckedStatus = (InIgnorePackages.Find(ItemName) != NULL) ? ECheckBoxState::Unchecked : ECheckBoxState::Checked; if (!(*ItItem)->IsDisabled()) { (*ItItem)->SetState(CheckedStatus); } } } void SPackagesDialog::PopulateIgnoreForSaveArray( OUT TSet& InOutIgnorePackages ) const { for( auto ItItem=Items.CreateConstIterator(); ItItem; ++ItItem ) { if((*ItItem)->GetState()==ECheckBoxState::Unchecked) { InOutIgnorePackages.Add( (*ItItem)->GetFileName() ); } else { InOutIgnorePackages.Remove( (*ItItem)->GetFileName() ); } } } void SPackagesDialog::Reset() { for( int32 ButtonIndex = 0; ButtonIndex < Buttons.Num(); ++ButtonIndex) { Buttons[ButtonIndex]->Reset(); } } FText SPackagesDialog::GetMessage() const { return Message; } FText SPackagesDialog::GetWarning() const { return Warning; } EVisibility SPackagesDialog::GetWarningVisibility() const { return (Warning.IsEmpty()) ? EVisibility::Collapsed : EVisibility::HitTestInvisible; } EColumnSortMode::Type SPackagesDialog::GetColumnSortMode( const FName ColumnId ) const { if ( SortByColumn != ColumnId ) { return EColumnSortMode::None; } return SortMode; } void SPackagesDialog::OnColumnSortModeChanged( const EColumnSortPriority::Type SortPriority, const FName& ColumnId, const EColumnSortMode::Type InSortMode ) { SortByColumn = ColumnId; SortMode = InSortMode; RequestSort(); } void SPackagesDialog::RequestSort() { bSortDirty = true; } void SPackagesDialog::SortTree() { int32 Sign; if (SortMode == EColumnSortMode::Ascending) { Sign = 1; } else if (SortMode == EColumnSortMode::Descending) { Sign = -1; } else { return; } constexpr int32 MaxComparers = 3; TArray&, const TSharedPtr&)>, TInlineAllocator> CompareColumns; CompareColumns.Reserve(MaxComparers); auto CompareAssetColumn = [](const TSharedPtr& A, const TSharedPtr& B) { return A->GetAssetDisplayName().Compare(B->GetAssetDisplayName()); }; // Prefer the Verse path if valid. It will be invalid if we are not showing Verse paths. auto ComparePackageColumn = [](const TSharedPtr& A, const TSharedPtr& B) { if (A->GetVersePath().IsValid() && B->GetVersePath().IsValid()) { return A->GetVersePath().Compare(B->GetVersePath()); } else if (A->GetVersePath().IsValid()) { return A->GetVersePath().ToString().Compare(B->GetPackageName()); } else if (B->GetVersePath().IsValid()) { return A->GetPackageName().Compare(B->GetVersePath().ToString()); } else { return A->GetPackageName().Compare(B->GetPackageName()); } }; if (SortByColumn == SPackagesDialogDefs::ColumnID_AssetLabel) { CompareColumns.Emplace(MoveTemp(CompareAssetColumn)); // Use package path as tie breaker. CompareColumns.Emplace(MoveTemp(ComparePackageColumn)); } else if (SortByColumn == SPackagesDialogDefs::ColumnID_PackageLabel) { CompareColumns.Emplace(MoveTemp(ComparePackageColumn)); } else { if (SortByColumn == SPackagesDialogDefs::ColumnID_IconLabel) { CompareColumns.Emplace([](const TSharedPtr& A, const TSharedPtr& B) { return A->GetIconName().Compare(B->GetIconName()); }); } else if (SortByColumn == SPackagesDialogDefs::ColumnID_OwnerLabel) { CompareColumns.Emplace([](const TSharedPtr& A, const TSharedPtr& B) { return A->GetOwnerName().Compare(B->GetOwnerName()); }); } else if (SortByColumn == SPackagesDialogDefs::ColumnID_TypeLabel) { CompareColumns.Emplace([](const TSharedPtr& A, const TSharedPtr& B) { return A->GetTypeName().CompareTo(B->GetTypeName()); }); } else if (SortByColumn == SPackagesDialogDefs::ColumnID_CheckedOutByLabel) { CompareColumns.Emplace([](const TSharedPtr& A, const TSharedPtr& B) { return A->GetCheckedOutByString().Compare(B->GetCheckedOutByString()); }); } else { return; } // Use asset name and package path as tie breakers. CompareColumns.Emplace(MoveTemp(CompareAssetColumn)); CompareColumns.Emplace(MoveTemp(ComparePackageColumn)); } Items.Sort([&CompareColumns, Sign](const TSharedPtr& A, const TSharedPtr& B) { for (const TFunction&, const TSharedPtr&)>& CompareColumn : CompareColumns) { const int32 Result = CompareColumn(A, B); if (Result != 0) { return Sign * Result < 0; } } return false; }); } void SPackageItemsListRow::Construct( const FArguments& InArgs, const TSharedRef& InOwnerTableView ) { PackagesDialogWeak = InArgs._PackagesDialog; Item = InArgs._Item; SMultiColumnTableRow< TSharedPtr >::Construct( FSuperRowType::FArguments() ,InOwnerTableView ); } TSharedRef SPackageItemsListRow::GenerateWidgetForColumn( const FName& ColumnName ) { // Create the widget for this item auto PackagesDialogShared = PackagesDialogWeak.Pin(); if (PackagesDialogShared.IsValid()) { return PackagesDialogShared->GenerateWidgetForItemAndColumn( Item, ColumnName ); } // Packages dialog no longer valid; return a valid, null widget. return SNullWidget::NullWidget; } #undef LOCTEXT_NAMESPACE