// Copyright Epic Games, Inc. All Rights Reserved. #include "SSourceControlChangelistRows.h" #include "ComponentReregisterContext.h" #include "FileHelpers.h" #include "ISourceControlModule.h" #include "PackageTools.h" #include "PackageSourceControlHelper.h" #include "SourceControlHelpers.h" #include "UncontrolledChangelistsModule.h" #include "SourceControlOperations.h" #include "UnsavedAssetsTrackerModule.h" #include "Styling/AppStyle.h" #include "Widgets/Images/SImage.h" #include "Widgets/Input/SButton.h" #include "Widgets/Text/STextBlock.h" #include "Algo/Transform.h" #include "Containers/Ticker.h" #include "RevisionControlStyle/RevisionControlStyle.h" #include "Styling/SlateStyleRegistry.h" #include "Styling/StarshipCoreStyle.h" #include "Misc/ComparisonUtility.h" #include "Misc/MessageDialog.h" #include "Widgets/Images/SThrobber.h" #include "Widgets/Layout/SWidgetSwitcher.h" #define LOCTEXT_NAMESPACE "SourceControlChangelistRow" namespace { bool CheckoutAndSavePackages(const TArray& Files) { FPackageSourceControlHelper PackageHelper; TArray PackageNames; PackageNames.Reserve(Files.Num()); for (const FString& Filename : Files) { PackageNames.Add(UPackageTools::FilenameToPackageName(Filename)); } if (PackageHelper.Checkout(PackageNames)) { TArray Packages; Packages.Reserve(PackageNames.Num()); { for (const FString& PackageName : PackageNames) { UPackage* Package = FindPackage(nullptr, *PackageName); if (ensure(Package)) { Packages.Add(Package); } else { Packages.Add(UPackageTools::LoadPackage(PackageName)); } } } constexpr bool bOnlyDirty = false; return UEditorLoadingAndSavingUtils::SavePackages(Packages, bOnlyDirty); } return false; } } FName SourceControlFileViewColumn::CheckBox::Id() { return TEXT("CheckBox"); } FName SourceControlFileViewColumn::Icon::Id() { return TEXT("Icon"); } FText SourceControlFileViewColumn::Icon::GetDisplayText() {return LOCTEXT("Name_Icon", "Revision Control Status"); } FText SourceControlFileViewColumn::Icon::GetToolTipText() { return LOCTEXT("Icon_Column_Tooltip", "Displays the asset/file status"); } FName SourceControlFileViewColumn::Shelve::Id() { return TEXT("Shelve"); } FText SourceControlFileViewColumn::Shelve::GetDisplayText() { return LOCTEXT("Name_Shelve", "Revision Control Shelve"); } FText SourceControlFileViewColumn::Shelve::GetToolTipText() { return LOCTEXT("Shelve_Column_Tooltip", "Displays the shelve status"); } FName SourceControlFileViewColumn::Name::Id() { return TEXT("Name"); } FText SourceControlFileViewColumn::Name::GetDisplayText() { return LOCTEXT("Name_Column", "Name"); } FText SourceControlFileViewColumn::Name::GetToolTipText() { return LOCTEXT("Name_Column_Tooltip", "Displays the asset/file name"); } FName SourceControlFileViewColumn::Path::Id() { return TEXT("Path"); } FText SourceControlFileViewColumn::Path::GetDisplayText() { return LOCTEXT("Path_Column", "Path"); } FText SourceControlFileViewColumn::Path::GetToolTipText() { return LOCTEXT("Path_Column_Tooltip", "Displays the asset/file path"); } FName SourceControlFileViewColumn::Type::Id() { return TEXT("Type"); } FText SourceControlFileViewColumn::Type::GetDisplayText() { return LOCTEXT("Type_Column", "Type"); } FText SourceControlFileViewColumn::Type::GetToolTipText() { return LOCTEXT("Type_Column_Tooltip", "Displays the asset type"); } FName SourceControlFileViewColumn::LastModifiedTimestamp::Id() { return TEXT("LastModifiedTimestamp"); } FText SourceControlFileViewColumn::LastModifiedTimestamp::GetDisplayText() {return LOCTEXT("LastModifiedTimestamp_Column", "Last Saved"); } FText SourceControlFileViewColumn::LastModifiedTimestamp::GetToolTipText() { return LOCTEXT("LastMofiedTimestamp_Column_Tooltip", "Displays the last time the file/asset was saved on user hard drive"); } FName SourceControlFileViewColumn::CheckedOutByUser::Id() { return TEXT("CheckedOutByUser"); } FText SourceControlFileViewColumn::CheckedOutByUser::GetDisplayText() { return LOCTEXT("CheckedOutByUser_Column", "User"); } FText SourceControlFileViewColumn::CheckedOutByUser::GetToolTipText() { return LOCTEXT("CheckedOutByUser_Column_Tooltip", "Displays the other user(s) that checked out the file/asset, if any"); } FName SourceControlFileViewColumn::Changelist::Id() { return TEXT("Changelist"); } FText SourceControlFileViewColumn::Changelist::GetDisplayText() { return LOCTEXT("Changelist_Column", "Changelist"); } FText SourceControlFileViewColumn::Changelist::GetToolTipText() { return LOCTEXT("Changelist_Column_Tooltip", "Displays the changelist the asset/file belongs to, if any"); } FName SourceControlFileViewColumn::Dirty::Id() { return TEXT("Dirty"); } FText SourceControlFileViewColumn::Dirty::GetDisplayText() { return LOCTEXT("Dirty_Column", "Unsaved"); } FText SourceControlFileViewColumn::Dirty::GetToolTipText() { return LOCTEXT("Dirty_Column_Tooltip", "Displays whether the asset/file has unsaved changes"); } FName SourceControlFileViewColumn::Discard::Id() { return TEXT("Discard"); } FText SourceControlFileViewColumn::Discard::GetDisplayText() { return LOCTEXT("Discard_Column", "Discard Unsaved Changes"); } FText SourceControlFileViewColumn::Discard::GetToolTipText() { return LOCTEXT("Discard_Column_Tooltip", "Provides option to discard unsaved changes to an asset/file"); } namespace SourceControlFileViewColumn { TFunction GetColumnComparer(FName ColumnId, EPathFlags PathFlags) { if (ColumnId == SourceControlFileViewColumn::CheckBox::Id()) { return [](const IFileViewTreeItem& Lhs, const IFileViewTreeItem& Rhs) { ECheckBoxState LhsVal = Lhs.GetCheckBoxState(); ECheckBoxState RhsVal = Rhs.GetCheckBoxState(); return LhsVal < RhsVal ? -1 : (LhsVal == RhsVal ? 0 : 1); }; } else if (ColumnId == SourceControlFileViewColumn::Icon::Id()) { return [](const IFileViewTreeItem& Lhs, const IFileViewTreeItem& Rhs) { int32 LhsVal = Lhs.GetIconSortingPriority(); int32 RhsVal = Rhs.GetIconSortingPriority(); return LhsVal < RhsVal ? -1 : (LhsVal == RhsVal ? 0 : 1); }; } else if (ColumnId == SourceControlFileViewColumn::Shelve::Id()) { return [](const IFileViewTreeItem& Lhs, const IFileViewTreeItem& Rhs) { const IChangelistTreeItem::TreeItemType LhsTreeItemType = Lhs.GetTreeItemType(); const IChangelistTreeItem::TreeItemType RhsTreeItemType = Rhs.GetTreeItemType(); return LhsTreeItemType < RhsTreeItemType ? -1 : (LhsTreeItemType == RhsTreeItemType ? 0 : 1); }; } else if (ColumnId == SourceControlFileViewColumn::Name::Id()) { return [](const IFileViewTreeItem& Lhs, const IFileViewTreeItem& Rhs) { return UE::ComparisonUtility::CompareNaturalOrder(*Lhs.GetName(), *Rhs.GetName()); }; } else if (ColumnId == SourceControlFileViewColumn::Path::Id()) { if (PathFlags == EPathFlags::ShowingPackageName) { return [](const IFileViewTreeItem& Lhs, const IFileViewTreeItem& Rhs) { return Lhs.GetPackageName().Compare(Rhs.GetPackageName()); }; } else if (PathFlags == EPathFlags::ShowingVersePath) { return [](const IFileViewTreeItem& Lhs, const IFileViewTreeItem& Rhs) { if (Lhs.GetVersePath().IsValid() && Rhs.GetVersePath().IsValid()) { return Lhs.GetVersePath().Compare(Rhs.GetVersePath()); } else if (Lhs.GetVersePath().IsValid()) { return Lhs.GetVersePath().ToString().Compare(Rhs.GetPath()); } else if (Rhs.GetVersePath().IsValid()) { return Lhs.GetPath().Compare(Rhs.GetVersePath().ToString()); } return Lhs.GetPath().Compare(Rhs.GetPath()); }; } else if (PathFlags == (EPathFlags::ShowingPackageName | EPathFlags::ShowingVersePath)) { return [](const IFileViewTreeItem& Lhs, const IFileViewTreeItem& Rhs) { if (Lhs.GetVersePath().IsValid() && Rhs.GetVersePath().IsValid()) { return Lhs.GetVersePath().Compare(Rhs.GetVersePath()); } else if (Lhs.GetVersePath().IsValid()) { return Lhs.GetVersePath().ToString().Compare(Rhs.GetPackageName()); } else if (Rhs.GetVersePath().IsValid()) { return Lhs.GetPackageName().Compare(Rhs.GetVersePath().ToString()); } return Lhs.GetPackageName().Compare(Rhs.GetPackageName()); }; } return [](const IFileViewTreeItem& Lhs, const IFileViewTreeItem& Rhs) { return Lhs.GetPath().Compare(Rhs.GetPath()); }; } else if (ColumnId == SourceControlFileViewColumn::Type::Id()) { return [](const IFileViewTreeItem& Lhs, const IFileViewTreeItem& Rhs) { return FCString::Stricmp(*Lhs.GetType(), *Rhs.GetType()); }; } else if (ColumnId == SourceControlFileViewColumn::LastModifiedTimestamp::Id()) { return [](const IFileViewTreeItem& Lhs, const IFileViewTreeItem& Rhs) { const FDateTime& LhsVal = Lhs.GetLastModifiedDateTime(); const FDateTime& RhsVal = Rhs.GetLastModifiedDateTime(); return LhsVal < RhsVal ? -1 : (LhsVal == RhsVal ? 0 : 1); }; } else if (ColumnId == SourceControlFileViewColumn::CheckedOutByUser::Id()) { return [](const IFileViewTreeItem& Lhs, const IFileViewTreeItem& Rhs) { return FCString::Stricmp(*Lhs.GetCheckedOutBy(), *Rhs.GetCheckedOutBy()); }; } else { checkNoEntry(); return [](const IFileViewTreeItem& Lhs, const IFileViewTreeItem& Rhs) { return &Lhs < &Rhs ? -1 : (&Lhs == &Rhs ? 0 : 1); }; }; } TFunction GetSortPredicate(EColumnSortMode::Type SortMode, FName ColumnId, EPathFlags PathFlags) { int32 Sign; if (SortMode == EColumnSortMode::Ascending) { Sign = 1; } else if (SortMode == EColumnSortMode::Descending) { Sign = -1; } else { return {}; } TArray> ColumnComparers; ColumnComparers.Reserve(3); ColumnComparers.Add(GetColumnComparer(ColumnId, PathFlags)); if (ColumnId != Path::Id()) { // Use name and path as tie breakers. if (ColumnId != Name::Id()) { ColumnComparers.Add(GetColumnComparer(Name::Id(), PathFlags)); } ColumnComparers.Add(GetColumnComparer(Path::Id(), PathFlags)); } return [Sign, ColumnComparers = MoveTemp(ColumnComparers)](const IFileViewTreeItem& Lhs, const IFileViewTreeItem& Rhs) { for (const TFunction& ColumnComparer : ColumnComparers) { const int32 Result = ColumnComparer(Lhs, Rhs); if (Result != 0) { return Sign * Result < 0; } } return false; }; } } FText FormatChangelistFileCountText(int32 DisplayedCount, int32 TotalCount) { return DisplayedCount == TotalCount ? FText::Format(INVTEXT("({0})"), TotalCount) : FText::Format(LOCTEXT("FilterNum", "({0} out of {1})"), DisplayedCount, TotalCount); } void SChangelistTableRow::Construct(const FArguments& InArgs, const TSharedRef& InOwner) { TreeItem = static_cast(InArgs._TreeItemToVisualize.Get()); OnPostDrop = InArgs._OnPostDrop; SetToolTipText(GetChangelistDescriptionText()); STableRow::Construct( STableRow::FArguments() .Style(FAppStyle::Get(), "TableView.Row") .Content() [ SNew(SHorizontalBox) +SHorizontalBox::Slot() // Icon .AutoWidth() [ SNew(SImage) .Image_Lambda([this]() { return (TreeItem != nullptr) ?FAppStyle::GetBrush(TreeItem->ChangelistState->GetSmallIconName()) : FAppStyle::GetBrush("SourceControl.Changelist"); }) ] +SHorizontalBox::Slot() // Changelist number. .Padding(2, 0, 0, 0) .AutoWidth() [ SNew(STextBlock) .Text(this, &SChangelistTableRow::GetChangelistText) .HighlightText(InArgs._HighlightText) ] +SHorizontalBox::Slot() // Files count. .Padding(4, 0, 4, 0) .AutoWidth() [ SNew(STextBlock) .Text_Lambda([this]() { // Check if the 'Shelved Files' node is currently linked to the tree view. (not filtered out). return FormatChangelistFileCountText(TreeItem->ShelvedChangelistItem->GetParent() ? TreeItem->GetChildren().Num() - 1 : TreeItem->GetChildren().Num(), TreeItem->GetFileCount()); }) ] +SHorizontalBox::Slot() // Description. .Padding(2, 0, 0, 0) .AutoWidth() [ SNew(STextBlock) .Text(this, &SChangelistTableRow::GetChangelistDescriptionSingleLineText) .HighlightText(InArgs._HighlightText) ] ], InOwner); } void SChangelistTableRow::PopulateSearchString(const FChangelistTreeItem& Item, TArray& OutStrings) { OutStrings.Emplace(Item.GetDisplayText().ToString()); // The changelist number OutStrings.Emplace(Item.GetDescriptionText().ToString()); // The changelist description. } FText SChangelistTableRow::GetChangelistText() const { return TreeItem->GetDisplayText(); } FText SChangelistTableRow::GetChangelistDescriptionText() const { return TreeItem->GetDescriptionText(); } FText SChangelistTableRow::GetChangelistDescriptionSingleLineText() const { using namespace SSourceControlCommon; return GetSingleLineChangelistDescription(TreeItem->GetDescriptionText(), ESingleLineFlags::NewlineConvertToSpace); } FReply SChangelistTableRow::OnDrop(const FGeometry& InGeometry, const FDragDropEvent& InDragDropEvent) { TSharedPtr DropOperation = InDragDropEvent.GetOperationAs(); if (DropOperation.IsValid()) { FSourceControlChangelistPtr DestChangelist = TreeItem->ChangelistState->GetChangelist(); check(DestChangelist.IsValid()); // NOTE: The UI don't show 'source controlled files' and 'uncontrolled files' at the same time. User cannot select and drag/drop both file types at the same time. if (!DropOperation->Files.IsEmpty()) { TArray Files; Algo::Transform(DropOperation->Files, Files, [](const FSourceControlStateRef& State) { return State->GetFilename(); }); SSourceControlCommon::ExecuteChangelistOperationWithSlowTaskWrapper(LOCTEXT("Dropping_Files_On_Changelist", "Moving file(s) to the selected changelist..."), [&]() { ISourceControlProvider& SourceControlProvider = ISourceControlModule::Get().GetProvider(); SourceControlProvider.Execute(ISourceControlOperation::Create(), DestChangelist, Files, EConcurrency::Synchronous, FSourceControlOperationComplete::CreateLambda( [](const TSharedRef& Operation, ECommandResult::Type InResult) { if (InResult == ECommandResult::Succeeded) { SSourceControlCommon::DisplaySourceControlOperationNotification(LOCTEXT("Drop_Files_On_Changelist_Succeeded", "File(s) successfully moved to the selected changelist."), SNotificationItem::CS_Success); } else if (InResult == ECommandResult::Failed) { SSourceControlCommon::DisplaySourceControlOperationNotification(LOCTEXT("Drop_Files_On_Changelist_Failed", "Failed to move the file(s) to the selected changelist."), SNotificationItem::CS_Fail); } })); }); } else if (!DropOperation->UncontrolledFiles.IsEmpty()) { // NOTE: This function does several operations that can fails but we don't get feedback. SSourceControlCommon::ExecuteUncontrolledChangelistOperationWithSlowTaskWrapper(LOCTEXT("Dropping_Uncontrolled_Files_On_Changelist", "Moving uncontrolled file(s) to the selected changelist..."), [&DropOperation, &DestChangelist]() { FUncontrolledChangelistsModule::Get().MoveFilesToControlledChangelist(DropOperation->UncontrolledFiles, DestChangelist, SSourceControlCommon::OpenConflictDialog); // TODO: Fix MoveFilesToControlledChangelist() to report the possible errors and display a notification. }); OnPostDrop.ExecuteIfBound(); } else if (!DropOperation->OfflineFiles.IsEmpty()) { const TArray& Files = DropOperation->OfflineFiles; SSourceControlCommon::ExecuteChangelistOperationWithSlowTaskWrapper(LOCTEXT("Dropping_Files_On_Changelist", "Moving file(s) to the selected changelist..."), [&]() { ISourceControlProvider& SourceControlProvider = ISourceControlModule::Get().GetProvider(); if (!CheckoutAndSavePackages(Files)) { return; } SourceControlProvider.Execute(ISourceControlOperation::Create(), DestChangelist, Files, EConcurrency::Synchronous, FSourceControlOperationComplete::CreateLambda( [](const TSharedRef& Operation, ECommandResult::Type InResult) { if (InResult == ECommandResult::Succeeded) { SSourceControlCommon::DisplaySourceControlOperationNotification(LOCTEXT("Drop_Files_On_Changelist_Succeeded", "File(s) successfully moved to the selected changelist."), SNotificationItem::CS_Success); } else if (InResult == ECommandResult::Failed) { SSourceControlCommon::DisplaySourceControlOperationNotification(LOCTEXT("Drop_Files_On_Changelist_Failed", "Failed to move the file(s) to the selected changelist."), SNotificationItem::CS_Fail); } })); }); } } return FReply::Handled(); } void SUncontrolledChangelistTableRow::Construct(const FArguments& InArgs, const TSharedRef& InOwner) { TreeItem = static_cast(InArgs._TreeItemToVisualize.Get()); OnPostDrop = InArgs._OnPostDrop; const FSlateBrush* IconBrush = (TreeItem != nullptr) ? FAppStyle::GetBrush(TreeItem->UncontrolledChangelistState->GetSmallIconName()) : FAppStyle::GetBrush("SourceControl.Changelist"); SetToolTipText(GetChangelistText()); STableRow::Construct( STableRow::FArguments() .Style(FAppStyle::Get(), "TableView.Row") .Content() [ SNew(SHorizontalBox) + SHorizontalBox::Slot() .AutoWidth() .VAlign(VAlign_Center) [ SNew(SImage) .Image(IconBrush) ] + SHorizontalBox::Slot() .AutoWidth() .Padding(2.0f, 0.0f) .VAlign(VAlign_Center) [ SNew(STextBlock) .Text(this, &SUncontrolledChangelistTableRow::GetChangelistText) .HighlightText(InArgs._HighlightText) ] +SHorizontalBox::Slot() // Files/Offline file count. .Padding(4, 0, 4, 0) .AutoWidth() [ SNew(STextBlock) .Text_Lambda([this]() { return FormatChangelistFileCountText(TreeItem->GetChildren().Num(), TreeItem->GetFileCount()); }) ] ], InOwner); } void SUncontrolledChangelistTableRow::PopulateSearchString(const FUncontrolledChangelistTreeItem& Item, TArray& OutStrings) { OutStrings.Emplace(Item.GetDisplayText().ToString()); } FText SUncontrolledChangelistTableRow::GetChangelistText() const { return TreeItem->GetDisplayText(); } FReply SUncontrolledChangelistTableRow::OnDrop(const FGeometry& InGeometry, const FDragDropEvent& InDragDropEvent) { TSharedPtr Operation = InDragDropEvent.GetOperationAs(); if (Operation.IsValid()) { if (Operation->OfflineFiles.IsEmpty()) { SSourceControlCommon::ExecuteUncontrolledChangelistOperationWithSlowTaskWrapper(LOCTEXT("Drag_File_To_Uncontrolled_Changelist", "Moving file(s) to the selected uncontrolled changelists..."), [this, &Operation]() { FUncontrolledChangelistsModule::Get().MoveFilesToUncontrolledChangelist(Operation->Files, Operation->UncontrolledFiles, TreeItem->UncontrolledChangelistState->Changelist); }); } // Drop unsaved assets (offline files) else { const TArray& Files = Operation->OfflineFiles; if (!CheckoutAndSavePackages(Files)) { return FReply::Unhandled(); } SSourceControlCommon::ExecuteUncontrolledChangelistOperationWithSlowTaskWrapper(LOCTEXT("Drag_File_To_Uncontrolled_Changelist", "Moving file(s) to the selected uncontrolled changelists..."), [this, &Files]() { FUncontrolledChangelistsModule::Get().MoveFilesToUncontrolledChangelist(Files, TreeItem->UncontrolledChangelistState->Changelist); }); } OnPostDrop.ExecuteIfBound(); } return FReply::Handled(); } void SUnsavedAssetsTableRow::Construct(const FArguments& InArgs, const TSharedRef& InOwner) { STableRow::Construct( STableRow::FArguments() .Style(FAppStyle::Get(), "TableView.Row") .Content() [ SNew(SHorizontalBox) + SHorizontalBox::Slot() .AutoWidth() .VAlign(VAlign_Center) [ SNew(SImage) .Image(FAppStyle::GetBrush("Assets.Unsaved")) ] + SHorizontalBox::Slot() .AutoWidth() .Padding(2.0f, 0.0f) .VAlign(VAlign_Center) [ SNew(STextBlock) .Text(LOCTEXT("SourceControl_Unsaved", "Unsaved")) ] +SHorizontalBox::Slot() // Files/Offline file count. .Padding(4, 0, 4, 0) .AutoWidth() [ SNew(STextBlock) .Text_Lambda([] { return FText::Format(FText::FromString("({0})"), FUnsavedAssetsTrackerModule::Get().GetUnsavedAssetNum()); }) ] ], InOwner); } void SFileTableRow::Construct(const FArguments& InArgs, const TSharedRef& InOwner) { TreeItem = static_cast(InArgs._TreeItemToVisualize.Get()); HighlightText = InArgs._HighlightText; PathFlags = InArgs._PathFlags; FSuperRowType::FArguments Args = FSuperRowType::FArguments() .OnDragDetected(InArgs._OnDragDetected) .ShowSelection(true); FSuperRowType::Construct(Args, InOwner); } TSharedRef SFileTableRow::GenerateWidgetForColumn(const FName& ColumnId) { auto ConstructPaddedWidget = [](const TSharedRef& Widget) { TSharedRef HorizontalBox = SNew(SHorizontalBox); HorizontalBox->AddSlot() .AutoWidth() .VAlign(VAlign_Center) .Padding(8, 0, 0, 0) [ Widget ]; return HorizontalBox; }; if (ColumnId == SourceControlFileViewColumn::CheckBox::Id()) { return SNew(SHorizontalBox) +SHorizontalBox::Slot() .Padding(FMargin(10.0f, 3.0f, 6.0f, 3.0f)) [ SNew(SCheckBox) .IsChecked(this, &SFileTableRow::GetCheckBoxState) .OnCheckStateChanged(this, &SFileTableRow::SetCheckBoxState) ]; } else if (ColumnId == SourceControlFileViewColumn::Icon::Id()) { return SNew(SBox) .WidthOverride(16) // Small Icons are usually 16x16 .HAlign(HAlign_Center) [ SSourceControlCommon::GetSCCStatusWidget(TreeItem->FileState) ]; } else if (ColumnId == SourceControlFileViewColumn::Shelve::Id()) { return SNew(SBox) .WidthOverride(16) // Small Icons are usually 16x16 .HAlign(HAlign_Center) [ SSourceControlCommon::GetSCCShelveWidget(TreeItem->IsShelved()) ]; } else if (ColumnId == SourceControlFileViewColumn::Name::Id()) { TSharedPtr Widget = SNew(STextBlock) .Text(this, &SFileTableRow::GetDisplayName) .ToolTipText(this, &SFileTableRow::GetDisplayName) .HighlightText(HighlightText); return ConstructPaddedWidget(Widget.ToSharedRef()); } else if (ColumnId == SourceControlFileViewColumn::Path::Id()) { TSharedPtr Widget = SNew(STextBlock) .Text(this, &SFileTableRow::GetDisplayPath) .ToolTipText(this, &SFileTableRow::GetFilename) .HighlightText(HighlightText); return ConstructPaddedWidget(Widget.ToSharedRef()); } else if (ColumnId == SourceControlFileViewColumn::Type::Id()) { TSharedPtr Widget = SNew(STextBlock) .Text(this, &SFileTableRow::GetDisplayType) .ToolTipText(this, &SFileTableRow::GetDisplayType) .ColorAndOpacity(this, &SFileTableRow::GetDisplayColor) .HighlightText(HighlightText); return ConstructPaddedWidget(Widget.ToSharedRef()); } else if (ColumnId == SourceControlFileViewColumn::LastModifiedTimestamp::Id()) { TSharedPtr Widget = SNew(STextBlock) .ToolTipText(this, &SFileTableRow::GetLastModifiedTimestamp) .Text(this, &SFileTableRow::GetLastModifiedTimestamp); return ConstructPaddedWidget(Widget.ToSharedRef()); } else if (ColumnId == SourceControlFileViewColumn::CheckedOutByUser::Id()) { TSharedPtr Widget = SNew(STextBlock) .ToolTipText(this, &SFileTableRow::GetCheckedOutByUser) .Text(this, &SFileTableRow::GetCheckedOutByUser); return ConstructPaddedWidget(Widget.ToSharedRef()); } else { return SNullWidget::NullWidget; } } void SFileTableRow::PopulateSearchString(const FFileTreeItem& Item, SourceControlFileViewColumn::EPathFlags PathFlags, TArray& OutStrings) { OutStrings.Emplace(Item.GetAssetName().ToString()); // Name if (EnumHasAnyFlags(PathFlags, SourceControlFileViewColumn::EPathFlags::ShowingVersePath) && Item.GetVersePath().IsValid()) { OutStrings.Emplace(Item.GetAssetVersePath().ToString()); } else if (EnumHasAnyFlags(PathFlags, SourceControlFileViewColumn::EPathFlags::ShowingPackageName)) { OutStrings.Emplace(Item.GetAssetPackageName().ToString()); // Path } else { OutStrings.Emplace(Item.GetAssetPath().ToString()); // Path } OutStrings.Emplace(Item.GetAssetType().ToString()); // Type OutStrings.Emplace(Item.GetLastModifiedTimestamp().ToString()); OutStrings.Emplace(Item.GetCheckedOutByUser().ToString()); } ECheckBoxState SFileTableRow::GetCheckBoxState() const { return TreeItem->GetCheckBoxState(); } void SFileTableRow::SetCheckBoxState(ECheckBoxState NewState) { TreeItem->SetCheckBoxState(NewState); } FText SFileTableRow::GetDisplayName() const { return TreeItem->GetAssetName(); } FText SFileTableRow::GetFilename() const { return TreeItem->GetFileName(); } FText SFileTableRow::GetDisplayPath() const { if (EnumHasAnyFlags(PathFlags.Get(SourceControlFileViewColumn::EPathFlags::Default), SourceControlFileViewColumn::EPathFlags::ShowingVersePath) && TreeItem->GetVersePath().IsValid()) { return TreeItem->GetAssetVersePath(); } else if (EnumHasAnyFlags(PathFlags.Get(SourceControlFileViewColumn::EPathFlags::Default), SourceControlFileViewColumn::EPathFlags::ShowingPackageName)) { return TreeItem->GetAssetPackageName(); } return TreeItem->GetAssetPath(); } FText SFileTableRow::GetDisplayType() const { return TreeItem->GetAssetType(); } FSlateColor SFileTableRow::GetDisplayColor() const { return TreeItem->GetAssetTypeColor(); } FText SFileTableRow::GetLastModifiedTimestamp() const { return TreeItem->GetLastModifiedTimestamp(); } FText SFileTableRow::GetCheckedOutByUser() const { return TreeItem->GetCheckedOutByUser(); } void SFileTableRow::OnDragEnter(FGeometry const& InGeometry, FDragDropEvent const& InDragDropEvent) { TSharedPtr DragOperation = InDragDropEvent.GetOperation(); DragOperation->SetCursorOverride(EMouseCursor::SlashedCircle); } void SFileTableRow::OnDragLeave(FDragDropEvent const& InDragDropEvent) { TSharedPtr DragOperation = InDragDropEvent.GetOperation(); DragOperation->SetCursorOverride(EMouseCursor::None); } void SShelvedFilesTableRow::Construct(const FArguments& InArgs, const TSharedRef& InOwnerTableView) { TreeItem = static_cast(InArgs._TreeItemToVisualize.Get()); STableRow::Construct( STableRow::FArguments() .Content() [ SNew(SHorizontalBox) +SHorizontalBox::Slot() .AutoWidth() .VAlign(VAlign_Center) .Padding(5, 0, 0, 0) [ SNew(SImage) .Image(FAppStyle::GetBrush("SourceControl.ShelvedChangelist")) ] +SHorizontalBox::Slot() .Padding(2.0f, 1.0f) .VAlign(VAlign_Center) .AutoWidth() [ SNew(STextBlock) .Text_Lambda([this](){ return TreeItem->GetDisplayText(); }) .HighlightText(InArgs._HighlightText) ] +SHorizontalBox::Slot() // Shelved file count. .Padding(4, 0, 4, 0) .AutoWidth() [ SNew(STextBlock) .Text_Lambda([this]() { return FormatChangelistFileCountText(TreeItem->GetChildren().Num(), static_cast(TreeItem->GetParent().Get())->GetShelvedFileCount()); }) ] ], InOwnerTableView); } void SShelvedFilesTableRow::PopulateSearchString(const FShelvedChangelistTreeItem& Item, TArray& OutStrings) { OutStrings.Emplace(Item.GetDisplayText().ToString()); } void SOfflineFileTableRow::Construct(const FArguments& InArgs, const TSharedRef& InOwner) { TreeItem = static_cast(InArgs._TreeItemToVisualize.Get()); HighlightText = InArgs._HighlightText; PathFlags = InArgs._PathFlags; FSuperRowType::FArguments Args = FSuperRowType::FArguments() .OnDragDetected(InArgs._OnDragDetected) .ShowSelection(true); FSuperRowType::Construct(Args, InOwner); } TSharedRef SOfflineFileTableRow::GenerateWidgetForColumn(const FName& ColumnId) { auto ConstructPaddedWidget = [](const TSharedRef& Widget) { TSharedRef HorizontalBox = SNew(SHorizontalBox); HorizontalBox->AddSlot() .AutoWidth() .VAlign(VAlign_Center) .Padding(8, 0, 0, 0) [ Widget ]; return HorizontalBox; }; if (ColumnId == SourceControlFileViewColumn::CheckBox::Id()) { return SNew(SHorizontalBox) +SHorizontalBox::Slot() .Padding(FMargin(10.0f, 3.0f, 6.0f, 3.0f)) [ SNew(SCheckBox) .IsChecked(this, &SOfflineFileTableRow::GetCheckBoxState) .OnCheckStateChanged(this, &SOfflineFileTableRow::SetCheckBoxState) ]; } else if (ColumnId == SourceControlFileViewColumn::Icon::Id()) { return SNew(SBox) .WidthOverride(16) // Small Icons are usually 16x16 .HAlign(HAlign_Center) .VAlign(VAlign_Center) [ SNew(SImage) .Image(FAppStyle::GetBrush(FName("SourceControl.OfflineFile_Small"))) ]; } if (ColumnId == SourceControlFileViewColumn::Shelve::Id()) { return SNew(SBox) .WidthOverride(16) // Small Icons are usually 16x16 .HAlign(HAlign_Center) [ SNullWidget::NullWidget ]; } else if (ColumnId == SourceControlFileViewColumn::Name::Id()) { TSharedPtr Widget = SNew(STextBlock) .ToolTipText(this, &SOfflineFileTableRow::GetDisplayName) .Text(this, &SOfflineFileTableRow::GetDisplayName) .HighlightText(HighlightText); return ConstructPaddedWidget(Widget.ToSharedRef()); } else if (ColumnId == SourceControlFileViewColumn::Path::Id()) { TSharedPtr Widget = SNew(STextBlock) .Text(this, &SOfflineFileTableRow::GetDisplayPath) .ToolTipText(this, &SOfflineFileTableRow::GetFilename) .HighlightText(HighlightText); return ConstructPaddedWidget(Widget.ToSharedRef()); } else if (ColumnId == SourceControlFileViewColumn::Type::Id()) { TSharedPtr Widget = SNew(STextBlock) .Text(this, &SOfflineFileTableRow::GetDisplayType) .ToolTipText(this, &SOfflineFileTableRow::GetDisplayType) .ColorAndOpacity(this, &SOfflineFileTableRow::GetDisplayColor) .HighlightText(HighlightText); return ConstructPaddedWidget(Widget.ToSharedRef()); } else if (ColumnId == SourceControlFileViewColumn::LastModifiedTimestamp::Id()) { TSharedPtr Widget = SNew(STextBlock) .ToolTipText(this, &SOfflineFileTableRow::GetLastModifiedTimestamp) .Text(this, &SOfflineFileTableRow::GetLastModifiedTimestamp); return ConstructPaddedWidget(Widget.ToSharedRef()); } else if (ColumnId == SourceControlFileViewColumn::CheckedOutByUser::Id()) { TSharedPtr Widget = SNew(STextBlock) .Text(FText::GetEmpty()); return ConstructPaddedWidget(Widget.ToSharedRef()); } else if (ColumnId == SourceControlFileViewColumn::Dirty::Id()) { if (FUnsavedAssetsTrackerModule::Get().IsAssetUnsaved(GetFilename().ToString())) { return SNew(SBox) .WidthOverride(16) // Small Icons are usually 16x16 .HAlign(HAlign_Center) [ SNew(SImage) .Image(FAppStyle::GetBrush(FName("SourceControl.OfflineFile_Small"))) ]; } return SNew(SBox) .WidthOverride(16); // Small Icons are usually 16x16 } else if (ColumnId == SourceControlFileViewColumn::Discard::Id()) { FString Filename = TreeItem->GetFullPathname(); if (!FUnsavedAssetsTrackerModule::Get().IsAssetUnsaved(Filename)) { return SNew(SBox) .WidthOverride(16); // Small Icons are usually 16x16 } TSharedRef DiscardSwitcher = SNew(SWidgetSwitcher); TSharedRef DiscardButton = SNew(SImage) .DesiredSizeOverride(FVector2D{ 16.0f }) .Image(FAppStyle::Get().GetBrush("Icons.XCircle")) .ColorAndOpacity(FSlateColor::UseSubduedForeground()) .OnMouseButtonDown_Lambda([this, DiscardSwitcher] (const FGeometry&, const FPointerEvent&) -> FReply { // Normalize packagenames and filenames FString PackageName; { FString TreeName = TreeItem->GetPackageName(); if (!FPackageName::TryConvertFilenameToLongPackageName(TreeName, PackageName)) { PackageName = MoveTemp(TreeName); } } // Validate we have a saved map if (UPackage* Package = FindPackage(nullptr, *PackageName)) { UPackage* LevelPackage = Package->GetOutermost(); if (LevelPackage == GetTransientPackage() || LevelPackage->HasAnyFlags(RF_Transient) || !FPackageName::IsValidLongPackageName(LevelPackage->GetName())) { FMessageDialog::Open(EAppMsgType::Ok, LOCTEXT("DiscardUnsavedChangesSaveMap", "You need to save the level before discarding unsaved changes.")); return FReply::Handled(); } } DiscardSwitcher->SetActiveWidgetIndex(1); ExecuteOnGameThread(UE_SOURCE_LOCATION, [this] { TArray PackageToReload { TreeItem->GetPackageName() }; const bool bAllowReloadWorld = true; const bool bInteractive = false; USourceControlHelpers::ApplyOperationAndReloadPackages( PackageToReload, [](const TArray&) -> bool { return true; }, bAllowReloadWorld, bInteractive ); }); return FReply::Handled(); }); DiscardSwitcher->AddSlot()[ DiscardButton ]; DiscardSwitcher->AddSlot()[ SNew(SCircularThrobber) .Radius(7.5f) ]; return SNew(SBox) .WidthOverride(16) // Small Icons are usually 16x16 .Padding(FMargin{1, 0}) .ToolTipText(LOCTEXT("UnsavedAsset_DiscardChanges", "Discard unsaved changes")) [ DiscardSwitcher ]; } else { return SNullWidget::NullWidget; } } void SOfflineFileTableRow::PopulateSearchString(const FOfflineFileTreeItem& Item, SourceControlFileViewColumn::EPathFlags PathFlags, TArray& OutStrings) { OutStrings.Emplace(Item.GetDisplayName().ToString()); // Name if (EnumHasAnyFlags(PathFlags, SourceControlFileViewColumn::EPathFlags::ShowingVersePath) && Item.GetVersePath().IsValid()) { OutStrings.Emplace(Item.GetDisplayVersePath().ToString()); } else if (EnumHasAnyFlags(PathFlags, SourceControlFileViewColumn::EPathFlags::ShowingPackageName)) { OutStrings.Emplace(Item.GetDisplayPackageName().ToString()); // Path } else { OutStrings.Emplace(Item.GetDisplayPath().ToString()); // Path } OutStrings.Emplace(Item.GetDisplayType().ToString()); // Type OutStrings.Emplace(Item.GetLastModifiedTimestamp().ToString()); } ECheckBoxState SOfflineFileTableRow::GetCheckBoxState() const { return TreeItem->GetCheckBoxState(); } void SOfflineFileTableRow::SetCheckBoxState(ECheckBoxState NewState) { TreeItem->SetCheckBoxState(NewState); } FText SOfflineFileTableRow::GetDisplayName() const { return TreeItem->GetDisplayName(); } FText SOfflineFileTableRow::GetFilename() const { return TreeItem->GetDisplayPackageName(); } FText SOfflineFileTableRow::GetDisplayPath() const { if (EnumHasAnyFlags(PathFlags.Get(SourceControlFileViewColumn::EPathFlags::Default), SourceControlFileViewColumn::EPathFlags::ShowingVersePath) && TreeItem->GetVersePath().IsValid()) { return TreeItem->GetDisplayVersePath(); } else if (EnumHasAnyFlags(PathFlags.Get(SourceControlFileViewColumn::EPathFlags::Default), SourceControlFileViewColumn::EPathFlags::ShowingPackageName)) { return TreeItem->GetDisplayPackageName(); } return TreeItem->GetDisplayPath(); } FText SOfflineFileTableRow::GetDisplayType() const { return TreeItem->GetDisplayType(); } FSlateColor SOfflineFileTableRow::GetDisplayColor() const { return TreeItem->GetDisplayColor(); } FText SOfflineFileTableRow::GetLastModifiedTimestamp() const { return TreeItem->GetLastModifiedTimestamp(); } void SOfflineFileTableRow::OnDragEnter(FGeometry const& InGeometry, FDragDropEvent const& InDragDropEvent) { TSharedPtr DragOperation = InDragDropEvent.GetOperation(); DragOperation->SetCursorOverride(EMouseCursor::SlashedCircle); } void SOfflineFileTableRow::OnDragLeave(FDragDropEvent const& InDragDropEvent) { TSharedPtr DragOperation = InDragDropEvent.GetOperation(); DragOperation->SetCursorOverride(EMouseCursor::None); } #undef LOCTEXT_NAMESPACE