// Copyright Epic Games, Inc. All Rights Reserved. #include "SSourceControlCommon.h" #include "Algo/Count.h" #include "Algo/Find.h" #include "AssetRegistry/AssetData.h" #include "ActorFolder.h" #include "ActorFolderDesc.h" #include "AssetToolsModule.h" #include "Styling/AppStyle.h" #include "ISourceControlModule.h" #include "SourceControlAssetDataCache.h" #include "SourceControlHelpers.h" #include "SSourceControlFileDialog.h" #include "Widgets/SOverlay.h" #include "Widgets/Images/SImage.h" #include "Widgets/Images/SLayeredImage.h" #include "Widgets/Layout/SBox.h" #include "Framework/Docking/TabManager.h" #include "Framework/Notifications/NotificationManager.h" #include "Misc/ScopedSlowTask.h" #include "Editor.h" #define LOCTEXT_NAMESPACE "SourceControlChangelist" ////////////////////////////////////////////////////////////////////////// FChangelistTreeItemPtr IChangelistTreeItem::GetParent() const { return Parent; } const TArray& IChangelistTreeItem::GetChildren() const { return Children; } void IChangelistTreeItem::AddChild(TSharedRef Child) { Child->Parent = AsShared(); Children.Add(MoveTemp(Child)); } void IChangelistTreeItem::RemoveChild(const TSharedRef& Child) { if (Children.Remove(Child)) { Child->Parent = nullptr; } } ////////////////////////////////////////////////////////////////////////// FFileTreeItem::FFileTreeItem(FSourceControlStateRef InFileState, bool bBeautifyPaths, bool bIsShelvedFile) : IChangelistTreeItem(bIsShelvedFile ? IChangelistTreeItem::ShelvedFile : IChangelistTreeItem::File) , FileState(InFileState) , MinTimeBetweenUpdate(FTimespan::FromSeconds(5.f)) , LastUpdateTime() , bAssetsUpToDate(false) { CheckBoxState = ECheckBoxState::Checked; // Initialize asset data first if (bBeautifyPaths) { FSourceControlAssetDataCache& AssetDataCache = ISourceControlModule::Get().GetAssetDataCache(); bAssetsUpToDate = AssetDataCache.GetAssetDataArray(FileState, Assets); } else { // We do not need to wait for AssetData from the cache. bAssetsUpToDate = true; } RefreshAssetInformation(); } void FFileTreeItem::RefreshAssetInformation() { // Initialize display-related members FString Filename = FileState->GetFilename(); FString TempAssetName = SSourceControlCommon::GetDefaultAssetName().ToString(); FString TempAssetPath = Filename; FString TempAssetType = SSourceControlCommon::GetDefaultAssetType().ToString(); FString TempPackageName = Filename; FColor TempAssetColor = 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 if (Assets.IsValid() && (Assets->Num() > 0)) { auto IsNotRedirector = [](const FAssetData& InAssetData) { return !InAssetData.IsRedirector(); }; int32 NumUserFacingAsset = Algo::CountIf(*Assets, IsNotRedirector); if (NumUserFacingAsset == 1) { const FAssetData& AssetData = *Algo::FindByPredicate(*Assets, IsNotRedirector); TempAssetName = RetrieveAssetName(AssetData); TempAssetPath = RetrieveAssetPath(AssetData); TempAssetType = AssetData.AssetClassPath.ToString(); const FAssetToolsModule& AssetToolsModule = FModuleManager::LoadModuleChecked(TEXT("AssetTools")); const TSharedPtr AssetTypeActions = AssetToolsModule.Get().GetAssetTypeActionsForClass(AssetData.GetClass()).Pin(); if (AssetTypeActions.IsValid()) { TempAssetColor = AssetTypeActions->GetTypeColor(); } else { TempAssetColor = FColor::White; } } else { TempAssetName = RetrieveAssetName((*Assets)[0]); TempAssetPath = RetrieveAssetPath((*Assets)[0]); for (int32 i = 1; i < Assets->Num(); ++i) { TempAssetName += TEXT(";") + RetrieveAssetName((*Assets)[i]); } TempAssetType = SSourceControlCommon::GetDefaultMultipleAsset().ToString(); TempAssetColor = FColor::White; } // Beautify the package name TempPackageName = TempAssetPath + "." + TempAssetName; } else if (FPackageName::TryConvertFilenameToLongPackageName(Filename, TempPackageName)) { // Fake asset name, asset path from the package name TempAssetPath = TempPackageName; int32 LastSlash = -1; if (TempPackageName.FindLastChar('/', LastSlash)) { TempAssetName = TempPackageName; TempAssetName.RightChopInline(LastSlash + 1); } } else { TempAssetName = FPaths::GetCleanFilename(Filename); TempPackageName = Filename; // put back original package name if the try failed TempAssetType = FText::Format(SSourceControlCommon::GetDefaultUnknownAssetType(), FText::FromString(FPaths::GetExtension(Filename).ToUpper())).ToString(); } // Finally, assign the temp variables to the member variables AssetName = FText::FromString(TempAssetName); AssetPath = FText::FromString(TempAssetPath); AssetType = FText::FromString(TempAssetType); AssetTypeColor = TempAssetColor; PackageName = FText::FromString(TempPackageName); } FText FFileTreeItem::GetAssetName() const { return AssetName; } FText FFileTreeItem::GetAssetName() { const FTimespan CurrentTime = FTimespan::FromSeconds(FPlatformTime::Seconds()); if ((!bAssetsUpToDate) && ((CurrentTime - LastUpdateTime) > MinTimeBetweenUpdate)) { FSourceControlAssetDataCache& AssetDataCache = ISourceControlModule::Get().GetAssetDataCache(); LastUpdateTime = CurrentTime; if (AssetDataCache.GetAssetDataArray(FileState, Assets)) { bAssetsUpToDate = true; RefreshAssetInformation(); } } return AssetName; } FString FFileTreeItem::RetrieveAssetName(const FAssetData& InAssetData) const { static const FName NAME_ActorLabel(TEXT("ActorLabel")); if (InAssetData.FindTag(NAME_ActorLabel)) { FString ResultAssetName = TEXT(""); InAssetData.GetTagValue(NAME_ActorLabel, ResultAssetName); return ResultAssetName; } else if (InAssetData.AssetClassPath == UActorFolder::StaticClass()->GetClassPathName()) { FString ActorFolderPath = UActorFolder::GetAssetRegistryInfoFromPackage(InAssetData.PackageName).GetDisplayName(); if (!ActorFolderPath.IsEmpty()) { return ActorFolderPath; } } return InAssetData.AssetName.ToString(); } FString FFileTreeItem::RetrieveAssetPath(const FAssetData& InAssetData) const { int32 LastDot = -1; FString Path = InAssetData.GetObjectPathString(); // Strip asset name from object path if (Path.FindLastChar('.', LastDot)) { Path.LeftInline(LastDot); } return Path; } ////////////////////////////////////////////////////////////////////////// FText FShelvedChangelistTreeItem::GetDisplayText() const { return FText::Format(LOCTEXT("SourceControl_ShelvedFiles", "Shelved Items ({0})"), Children.Num()); } ////////////////////////////////////////////////////////////////////////// FOfflineFileTreeItem::FOfflineFileTreeItem(const FString& InFilename) : IChangelistTreeItem(IChangelistTreeItem::OfflineFile) , Assets() , Filename(InFilename) , PackageName(FText::FromString(InFilename)) , AssetName(SSourceControlCommon::GetDefaultAssetName()) , AssetPath() , AssetType(SSourceControlCommon::GetDefaultAssetType()) , AssetTypeColor() { FString TempString; USourceControlHelpers::GetAssetData(InFilename, Assets); if (Assets.Num() > 0) { const FAssetData& AssetData = Assets[0]; AssetPath = FText::FromString(AssetData.GetObjectPathString()); // Find name, asset type & color only if there is exactly one asset if (Assets.Num() == 1) { static FName NAME_ActorLabel(TEXT("ActorLabel")); if (AssetData.FindTag(NAME_ActorLabel)) { AssetData.GetTagValue(NAME_ActorLabel, AssetName); } else { AssetName = FText::FromName(AssetData.AssetName); } AssetType = FText::FromString(AssetData.AssetClassPath.ToString()); const FAssetToolsModule& AssetToolsModule = FModuleManager::LoadModuleChecked(TEXT("AssetTools")); const TSharedPtr AssetTypeActions = AssetToolsModule.Get().GetAssetTypeActionsForClass(AssetData.GetClass()).Pin(); if (AssetTypeActions.IsValid()) { AssetTypeColor = AssetTypeActions->GetTypeColor(); } else { AssetTypeColor = FColor::White; } } else { AssetType = SSourceControlCommon::GetDefaultMultipleAsset(); AssetTypeColor = FColor::White; } // Beautify the package name PackageName = AssetPath; } else if (FPackageName::TryConvertFilenameToLongPackageName(InFilename, TempString)) { PackageName = FText::FromString(TempString); // Fake asset name, asset path from the package name AssetPath = PackageName; } else { AssetName = FText::FromString(FPaths::GetCleanFilename(InFilename)); AssetType = FText::Format(SSourceControlCommon::GetDefaultUnknownAssetType(), FText::FromString(FPaths::GetExtension(InFilename).ToUpper())); } } ////////////////////////////////////////////////////////////////////////// namespace SSourceControlCommon { TSharedRef GetSCCFileWidget(FSourceControlStateRef InFileState, bool bIsShelvedFile) { const FSlateBrush* IconBrush = FAppStyle::GetBrush("ContentBrowser.ColumnViewAssetIcon"); // Make icon overlays (eg, SCC and dirty status) a reasonable size in relation to the icon size (note: it is assumed this icon is square) const float ICON_SCALING_FACTOR = 0.7f; const float IconOverlaySize = IconBrush->ImageSize.X * ICON_SCALING_FACTOR; return SNew(SOverlay) // The actual icon + SOverlay::Slot() [ SNew(SImage) .Image(IconBrush) .ColorAndOpacity_Lambda([bIsShelvedFile]() -> FSlateColor { return FSlateColor(bIsShelvedFile ? FColor::Yellow : FColor::White); }) ] // Source control state + SOverlay::Slot() .HAlign(HAlign_Left) .VAlign(VAlign_Top) [ SNew(SBox) .WidthOverride(IconOverlaySize) .HeightOverride(IconOverlaySize) [ SNew(SLayeredImage, InFileState->GetIcon()) .ToolTipText(InFileState->GetDisplayTooltip()) ] ]; } FText GetDefaultAssetName() { return LOCTEXT("SourceControl_DefaultAssetName", "Unavailable"); } FText GetDefaultAssetType() { return LOCTEXT("SourceControl_DefaultAssetType", "Unknown"); } FText GetDefaultUnknownAssetType() { return LOCTEXT("SourceControl_FileTypeDefault", "{0} File"); } FText GetDefaultMultipleAsset() { return LOCTEXT("SourceCOntrol_ManyAssetType", "Multiple Assets"); } /** Wraps the execution of a changelist operations with a slow task. */ void ExecuteChangelistOperationWithSlowTaskWrapper(const FText& Message, const TFunction& ChangelistTask) { // NOTE: This is a ugly workaround for P4 because the generic popup feedback operations in FScopedSourceControlProgress() was supressed for all synchrounous // operations. For other source control providers, the popup still shows up and showing a slow task and the FScopedSourceControlProgress at the same // time is a bad user experience. Until we fix source control popup situation in general in the Editor, this hack is in place to avoid the double popup. // At the time of writing, the other source control provider that supports changelists is Plastic. if (ISourceControlModule::Get().GetProvider().GetName() == "Perforce") { FScopedSlowTask Progress(0.f, Message); Progress.MakeDialog(); ChangelistTask(); } else { ChangelistTask(); } } /** Wraps the execution of an uncontrolled changelist operations with a slow task. */ void ExecuteUncontrolledChangelistOperationWithSlowTaskWrapper(const FText& Message, const TFunction& UncontrolledChangelistTask) { ExecuteChangelistOperationWithSlowTaskWrapper(Message, UncontrolledChangelistTask); } /** Displays toast notification to report the status of task. */ void DisplaySourceControlOperationNotification(const FText& Message, SNotificationItem::ECompletionState CompletionState) { if (Message.IsEmpty()) { return; } FNotificationInfo NotificationInfo(Message); NotificationInfo.ExpireDuration = 6.0f; NotificationInfo.Hyperlink = FSimpleDelegate::CreateLambda([]() { FGlobalTabmanager::Get()->TryInvokeTab(FName("OutputLog")); }); NotificationInfo.HyperlinkText = LOCTEXT("ShowOutputLogHyperlink", "Show Output Log"); FSlateNotificationManager::Get().AddNotification(NotificationInfo)->SetCompletionState(CompletionState); } bool OpenConflictDialog(const TArray& InFilesConflicts) { TSharedPtr Window; TSharedPtr SourceControlFileDialog; Window = SNew(SWindow) .Title(LOCTEXT("CheckoutPackagesDialogTitle", "Check Out Assets")) .SizingRule(ESizingRule::UserSized) .ClientSize(FVector2D(1024.0f, 512.0f)) .SupportsMaximize(false) .SupportsMinimize(false) [ SNew(SBorder) .Padding(4.f) .BorderImage(FAppStyle::GetBrush("ToolPanel.GroupBorder")) [ SAssignNew(SourceControlFileDialog, SSourceControlFileDialog) .Message(LOCTEXT("CheckoutPackagesDialogMessage", "Conflict detected in the following assets:")) .Warning(LOCTEXT("CheckoutPackagesWarnMessage", "Warning: These assets are locked or not at the head revision. You may lose your changes if you continue, as you will be unable to submit them to source control.")) .Files(InFilesConflicts) ] ]; SourceControlFileDialog->SetWindow(Window); Window->SetWidgetToFocusOnActivate(SourceControlFileDialog); GEditor->EditorAddModalWindow(Window.ToSharedRef()); return SourceControlFileDialog->IsProceedButtonPressed(); } } // end of namespace SSourceControlCommon #undef LOCTEXT_NAMESPACE