// Copyright Epic Games, Inc. All Rights Reserved. #include "SSourceControlChangelists.h" #include "EditorStyleSet.h" #include "Algo/Transform.h" #include "Widgets/DeclarativeSyntaxSupport.h" #include "Widgets/Images/SImage.h" #include "Widgets/Layout/SScrollBorder.h" #include "Widgets/SOverlay.h" #include "ISourceControlProvider.h" #include "ISourceControlModule.h" #include "SourceControlOperations.h" #include "ToolMenus.h" #include "Widgets/Images/SLayeredImage.h" #include "SSourceControlDescription.h" #include "SourceControlWindows.h" #include "AssetToolsModule.h" #include "ContentBrowserModule.h" #include "IContentBrowserSingleton.h" #include "Misc/MessageDialog.h" #include "AssetRegistry/AssetRegistryModule.h" #define LOCTEXT_NAMESPACE "SourceControlChangelist" ////////////////////////////// static TSharedRef GetSCCFileWidget(FSourceControlStateRef InFileState) { const FSlateBrush* IconBrush = FEditorStyle::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) ] // Source control state + SOverlay::Slot() .HAlign(HAlign_Left) .VAlign(VAlign_Top) [ SNew(SBox) .WidthOverride(IconOverlaySize) .HeightOverride(IconOverlaySize) [ SNew(SLayeredImage, InFileState->GetIcon()) ] ]; } struct FSCCFileDragDropOp : public FDragDropOperation { DRAG_DROP_OPERATOR_TYPE(FSCCFileDragDropOp, FDragDropOperation); using FDragDropOperation::Construct; virtual TSharedPtr GetDefaultDecorator() const override { return GetSCCFileWidget(Files[0]); } TArray Files; }; ////////////////////////////// struct FChangelistTreeItem : public IChangelistTreeItem { FChangelistTreeItem(FSourceControlChangelistStateRef InChangelistState) : ChangelistState(InChangelistState) { Type = IChangelistTreeItem::Changelist; } FText GetDisplayText() const { return ChangelistState->GetDisplayText(); } FText GetDescriptionText() const { return ChangelistState->GetDescriptionText(); } FSourceControlChangelistStateRef ChangelistState; }; struct FFileTreeItem : public IChangelistTreeItem { FFileTreeItem(FSourceControlStateRef InFileState) : FileState(InFileState) { Type = IChangelistTreeItem::File; FString Filename = FileState->GetFilename(); FText AssetName = LOCTEXT("SourceControl_DefaultAssetName", "None"); FText AssetPath; FText AssetType = LOCTEXT("SourceControl_DefaultAssetType", "Unknown"); FString AssetPackageName; FColor AssetColor = 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 (FPackageName::TryConvertFilenameToLongPackageName(Filename, AssetPackageName)) { AssetPath = FText::FromString(AssetPackageName); TArray Assets; FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked(TEXT("AssetRegistry")); AssetRegistryModule.Get().GetAssetsByPackageName(*AssetPackageName, Assets); if (Assets.Num()) { AssetName = FText::FromString(Assets[0].AssetName.ToString()); AssetColor = FColor::White; if (Assets.Num() > 1) { AssetType = LOCTEXT("SourceCOntrol_ManyAssetType", "Multiple Assets"); } else { AssetType = FText::FromString(Assets[0].AssetClass.ToString()); const FAssetToolsModule& AssetToolsModule = FModuleManager::LoadModuleChecked(TEXT("AssetTools")); const TSharedPtr AssetTypeActions = AssetToolsModule.Get().GetAssetTypeActionsForClass(Assets[0].GetClass()).Pin(); if (AssetTypeActions.IsValid()) { AssetColor = AssetTypeActions->GetTypeColor(); } } } } else { AssetPath = FText::FromString(Filename); } DisplayName = AssetName; DisplayPath = AssetPath; DisplayType = AssetType; DisplayColor = AssetColor; } FText GetDisplayPath() const { return DisplayPath; } FText GetDisplayName() const { return DisplayName; } FText GetDisplayType() const { return DisplayType; } FSlateColor GetDisplayColor() const { return FSlateColor(DisplayColor); } FSourceControlStateRef FileState; private: FText DisplayPath; FText DisplayName; FText DisplayType; FColor DisplayColor; }; SSourceControlChangelistsWidget::SSourceControlChangelistsWidget() { } void SSourceControlChangelistsWidget::Construct(const FArguments& InArgs) { // Register delegates ISourceControlModule& SCCModule = ISourceControlModule::Get(); SCCModule.RegisterProviderChanged(FSourceControlProviderChanged::FDelegate::CreateSP(this, &SSourceControlChangelistsWidget::OnSourceControlProviderChanged)); SourceControlStateChangedDelegateHandle = SCCModule.GetProvider().RegisterSourceControlStateChanged_Handle(FSourceControlStateChanged::FDelegate::CreateSP(this, &SSourceControlChangelistsWidget::OnSourceControlStateChanged)); TreeView = CreateTreeviewWidget(); ChildSlot [ SNew(SScrollBorder, TreeView.ToSharedRef()) .Visibility(TAttribute::Create(TAttribute::FGetter::CreateLambda([]()->EVisibility { return ISourceControlModule::Get().IsEnabled() ? EVisibility::Visible : EVisibility::Hidden; }))) [ TreeView.ToSharedRef() ] ]; bShouldRefresh = true; } void SSourceControlChangelistsWidget::Tick(const FGeometry& AllottedGeometry, const double InCurrentTime, const float InDeltaTime) { if (bShouldRefresh) { if (ISourceControlModule::Get().IsEnabled()) { RequestRefresh(); bShouldRefresh = false; } else { // No provider available, clear changelist tree ClearChangelistsTree(); } } } void SSourceControlChangelistsWidget::RequestRefresh() { if (ISourceControlModule::Get().IsEnabled()) { TSharedRef UpdatePendingChangelistsOperation = ISourceControlOperation::Create(); UpdatePendingChangelistsOperation->SetUpdateAllChangelists(true); UpdatePendingChangelistsOperation->SetUpdateFilesStates(true); ISourceControlProvider& SourceControlProvider = ISourceControlModule::Get().GetProvider(); SourceControlProvider.Execute(UpdatePendingChangelistsOperation, EConcurrency::Asynchronous, FSourceControlOperationComplete::CreateSP(this, &SSourceControlChangelistsWidget::OnChangelistsStatusUpdated)); } else { // No provider available, clear changelist tree ClearChangelistsTree(); } } void SSourceControlChangelistsWidget::ClearChangelistsTree() { if (!ChangelistsNodes.IsEmpty()) { ChangelistsNodes.Empty(); TreeView->RequestTreeRefresh(); } } void SSourceControlChangelistsWidget::Refresh() { if (ISourceControlModule::Get().IsEnabled()) { ISourceControlProvider& SourceControlProvider = ISourceControlModule::Get().GetProvider(); TArray Changelists = SourceControlProvider.GetChangelists(EStateCacheUsage::Use); TArray ChangelistsStates; SourceControlProvider.GetState(Changelists, ChangelistsStates, EStateCacheUsage::Use); ChangelistsNodes.Reset(ChangelistsStates.Num()); for (FSourceControlChangelistStateRef ChangelistState : ChangelistsStates) { FChangelistTreeItemRef ChangelistTreeItem = MakeShareable(new FChangelistTreeItem(ChangelistState)); for (FSourceControlStateRef FileRef : ChangelistState->GetFilesStates()) { FChangelistTreeItemRef FileTreeItem = MakeShareable(new FFileTreeItem(FileRef)); ChangelistTreeItem->AddChild(FileTreeItem); } ChangelistsNodes.Add(ChangelistTreeItem); } TreeView->RequestTreeRefresh(); } else { ClearChangelistsTree(); } } void SSourceControlChangelistsWidget::OnSourceControlProviderChanged(ISourceControlProvider& OldProvider, ISourceControlProvider& NewProvider) { OldProvider.UnregisterSourceControlStateChanged_Handle(SourceControlStateChangedDelegateHandle); SourceControlStateChangedDelegateHandle = NewProvider.RegisterSourceControlStateChanged_Handle(FSourceControlStateChanged::FDelegate::CreateSP(this, &SSourceControlChangelistsWidget::OnSourceControlStateChanged)); bShouldRefresh = true; } void SSourceControlChangelistsWidget::OnSourceControlStateChanged() { Refresh(); } void SSourceControlChangelistsWidget::OnChangelistsStatusUpdated(const FSourceControlOperationRef& InOperation, ECommandResult::Type InType) { Refresh(); } void SChangelistTree::Private_SetItemSelection(FChangelistTreeItemPtr TheItem, bool bShouldBeSelected, bool bWasUserDirected) { bool bAllowSelectionChange = true; if (bShouldBeSelected && !SelectedItems.IsEmpty()) { // Prevent selecting changelists and files at the same time. FChangelistTreeItemPtr CurrentlySelectedItem = (*SelectedItems.begin()); if (TheItem->GetTreeItemType() != CurrentlySelectedItem->GetTreeItemType()) { bAllowSelectionChange = false; } // Prevent selecting items that don't share the same root else if (TheItem->GetParent() != CurrentlySelectedItem->GetParent()) { bAllowSelectionChange = false; } } if (bAllowSelectionChange) { STreeView::Private_SetItemSelection(TheItem, bShouldBeSelected, bWasUserDirected); } } FSourceControlChangelistStatePtr SSourceControlChangelistsWidget::GetCurrentChangelistState() { if (!TreeView) { return nullptr; } TArray SelectedItems = TreeView->GetSelectedItems(); if (SelectedItems.Num() != 1 || SelectedItems[0]->GetTreeItemType() != IChangelistTreeItem::Changelist) { return nullptr; } else { return StaticCastSharedPtr(SelectedItems[0])->ChangelistState; } } FSourceControlChangelistPtr SSourceControlChangelistsWidget::GetCurrentChangelist() { FSourceControlChangelistStatePtr ChangelistState = GetCurrentChangelistState(); return ChangelistState ? (FSourceControlChangelistPtr)(ChangelistState->GetChangelist()) : nullptr; } FSourceControlChangelistStatePtr SSourceControlChangelistsWidget::GetChangelistStateFromFileSelection() { if (!TreeView) { return nullptr; } TArray SelectedItems = TreeView->GetSelectedItems(); if (SelectedItems.Num() == 0 || SelectedItems[0]->GetTreeItemType() != IChangelistTreeItem::File) { return nullptr; } else { return StaticCastSharedPtr(SelectedItems[0]->GetParent())->ChangelistState; } } FSourceControlChangelistPtr SSourceControlChangelistsWidget::GetChangelistFromFileSelection() { FSourceControlChangelistStatePtr ChangelistState = GetChangelistStateFromFileSelection(); return ChangelistState ? (FSourceControlChangelistPtr)(ChangelistState->GetChangelist()) : nullptr; } TArray SSourceControlChangelistsWidget::GetSelectedFiles() { TArray SelectedItems = TreeView->GetSelectedItems(); if (SelectedItems.Num() == 0 || SelectedItems[0]->GetTreeItemType() != IChangelistTreeItem::File) { return TArray(); } else { TArray Files; for (FChangelistTreeItemPtr Item : SelectedItems) { Files.Add(StaticCastSharedPtr(Item)->FileState->GetFilename()); } return Files; } } void SSourceControlChangelistsWidget::OnNewChangelist() { FText ChangelistDescription; bool bOk = GetChangelistDescription( nullptr, LOCTEXT("SourceControl.Changelist.New.Title", "New Changelist..."), LOCTEXT("SourceControl.Changelist.New.Label", "Enter a description for the changelist:"), ChangelistDescription); if (!bOk) { return; } ISourceControlProvider& SourceControlProvider = ISourceControlModule::Get().GetProvider(); auto NewChangelistOperation = ISourceControlOperation::Create(); NewChangelistOperation->SetDescription(ChangelistDescription); SourceControlProvider.Execute(NewChangelistOperation); Refresh(); } void SSourceControlChangelistsWidget::OnDeleteChangelist() { if (GetCurrentChangelist() == nullptr) { return; } ISourceControlProvider& SourceControlProvider = ISourceControlModule::Get().GetProvider(); SourceControlProvider.Execute(ISourceControlOperation::Create(), GetCurrentChangelist()); Refresh(); } bool SSourceControlChangelistsWidget::CanDeleteChangelist() { FSourceControlChangelistStatePtr Changelist = GetCurrentChangelistState(); return Changelist != nullptr && Changelist->GetFilesStates().Num() == 0 && Changelist->GetShelvedFilesStates().Num() == 0; } void SSourceControlChangelistsWidget::OnEditChangelist() { FSourceControlChangelistStatePtr ChangelistState = GetCurrentChangelistState(); if(ChangelistState == nullptr) { return; } FText NewChangelistDescription = ChangelistState->GetDescriptionText(); bool bOk = GetChangelistDescription( nullptr, LOCTEXT("SourceControl.Changelist.New.Title", "Edit Changelist..."), LOCTEXT("SourceControl.Changelist.New.Label", "Enter a new description for the changelist:"), NewChangelistDescription); if (!bOk) { return; } auto EditChangelistOperation = ISourceControlOperation::Create(); EditChangelistOperation->SetDescription(NewChangelistDescription); ISourceControlProvider& SourceControlProvider = ISourceControlModule::Get().GetProvider(); SourceControlProvider.Execute(EditChangelistOperation, ChangelistState->GetChangelist()); Refresh(); } void SSourceControlChangelistsWidget::OnRevertUnchanged() { ISourceControlProvider& SourceControlProvider = ISourceControlModule::Get().GetProvider(); auto RevertUnchangedOperation = ISourceControlOperation::Create(); if(GetCurrentChangelist() != nullptr) { // Operation on full changelist SourceControlProvider.Execute(RevertUnchangedOperation, GetCurrentChangelist()); } else { // Operation on files in a changelist SourceControlProvider.Execute(RevertUnchangedOperation, GetChangelistFromFileSelection(), GetSelectedFiles()); } Refresh(); } void SSourceControlChangelistsWidget::OnRevert() { FText DialogText; FText DialogTitle; const bool bApplyOnChangelist = (GetCurrentChangelist() != nullptr); if (bApplyOnChangelist) { DialogText = LOCTEXT("SourceControl_ConfirmRevertChangelist", "Are you sure you want to revert this changelist?"); DialogTitle = LOCTEXT("SourceControl_ConfirmRevertChangelist_Title", "Confirm changelist revert"); } else { DialogText = LOCTEXT("SourceControl_ConfirmRevertFiles", "Are you sure you want to revert the selected files?"); DialogTitle = LOCTEXT("SourceControl_ConfirmReverFiles_Title", "Confirm files revert"); } EAppReturnType::Type UserConfirmation = FMessageDialog::Open(EAppMsgType::OkCancel, EAppReturnType::Ok, DialogText, &DialogTitle); if (UserConfirmation != EAppReturnType::Ok) { return; } ISourceControlProvider& SourceControlProvider = ISourceControlModule::Get().GetProvider(); auto RevertOperation = ISourceControlOperation::Create(); if (GetCurrentChangelist() != nullptr) { // Operation on full changelist SourceControlProvider.Execute(RevertOperation, GetCurrentChangelist()); } else { // Operation on files in a changelist SourceControlProvider.Execute(RevertOperation, GetChangelistFromFileSelection(), GetSelectedFiles()); } Refresh(); } void SSourceControlChangelistsWidget::OnSubmitChangelist() { FSourceControlChangelistPtr Changelist = GetCurrentChangelist(); if (!Changelist) { return; } const FText DialogText = LOCTEXT("SourceControl_ConfirmSubmit", "Are you sure you want to submit this changelist?"); const FText DialogTitle = LOCTEXT("SourceControl_ConfirmSubmit_Title", "Confirm changelist submit"); EAppReturnType::Type UserConfirmation = FMessageDialog::Open(EAppMsgType::OkCancel, EAppReturnType::Ok, DialogText, & DialogTitle); if (UserConfirmation == EAppReturnType::Ok) { ISourceControlProvider& SourceControlProvider = ISourceControlModule::Get().GetProvider(); auto SubmitChangelistOperation = ISourceControlOperation::Create(); SourceControlProvider.Execute(SubmitChangelistOperation, Changelist); Refresh(); } } bool SSourceControlChangelistsWidget::CanSubmitChangelist() { FSourceControlChangelistStatePtr Changelist = GetCurrentChangelistState(); return Changelist != nullptr && Changelist->GetFilesStates().Num() >= 0 && Changelist->GetShelvedFilesStates().Num() == 0; } void SSourceControlChangelistsWidget::OnLocateFile() { FAssetToolsModule& AssetToolsModule = FModuleManager::LoadModuleChecked(TEXT("AssetTools")); TArray AssetsToSync; TArray SelectedFiles = GetSelectedFiles(); AssetsToSync.Reserve(SelectedFiles.Num()); for (const FString& SelectedFile : SelectedFiles) { FString AssetPackageName; if (FPackageName::TryConvertFilenameToLongPackageName(SelectedFile, AssetPackageName)) { UPackage* AssetPackage = LoadPackage(nullptr, *AssetPackageName, LOAD_ForDiff | LOAD_DisableCompileOnLoad); // grab the asset from the package - we assume asset name matches file name FString AssetName = FPaths::GetBaseFilename(SelectedFile); UObject* SelectedAsset = FindObject(AssetPackage, *AssetName); if (SelectedAsset) { AssetsToSync.Add(SelectedAsset); } } } if (AssetsToSync.Num() > 0) { FContentBrowserModule& ContentBrowserModule = FModuleManager::Get().LoadModuleChecked("ContentBrowser"); ContentBrowserModule.Get().SyncBrowserToAssets(AssetsToSync, true); } } bool SSourceControlChangelistsWidget::CanLocateFile() { return GetSelectedFiles().Num() > 0; } void SSourceControlChangelistsWidget::OnShowHistory() { TArray SelectedFiles = GetSelectedFiles(); if (SelectedFiles.Num() > 0) { FSourceControlWindows::DisplayRevisionHistory(SelectedFiles); } } void SSourceControlChangelistsWidget::OnDiffAgainstDepot() { TArray SelectedFiles = GetSelectedFiles(); if (SelectedFiles.Num() > 0) { FSourceControlWindows::DiffAgainstWorkspace(SelectedFiles[0]); } } bool SSourceControlChangelistsWidget::CanDiffAgainstDepot() { return GetSelectedFiles().Num() == 1; } TSharedPtr SSourceControlChangelistsWidget::OnOpenContextMenu() { UToolMenus* ToolMenus = UToolMenus::Get(); static const FName MenuName = "SourceControl.ChangelistContextMenu"; if (!ToolMenus->IsMenuRegistered(MenuName)) { ToolMenus->RegisterMenu(MenuName); } // Build up the menu for a selection FToolMenuContext Context; UToolMenu* Menu = ToolMenus->GenerateMenu(MenuName, Context); bool bHasSelectedChangelist = (GetCurrentChangelist() != nullptr); bool bHasEmptySelection = (GetCurrentChangelist() == nullptr && GetSelectedFiles().Num() == 0); FToolMenuSection& Section = Menu->AddSection("Source Control"); // This should appear only on change lists if (bHasSelectedChangelist) { Section.AddMenuEntry("SubmitChangelist", LOCTEXT("SourceControl_SubmitChangelist", "Submit Changelist"), LOCTEXT("SourceControl_SubmitChangeslit_Tooltip", "Submits a changelist"), FSlateIcon(), FUIAction( FExecuteAction::CreateSP(this, &SSourceControlChangelistsWidget::OnSubmitChangelist), FCanExecuteAction::CreateSP(this, &SSourceControlChangelistsWidget::CanSubmitChangelist))); } // This can appear on both files & changelist if (!bHasEmptySelection) { Section.AddMenuEntry("RevertUnchanged", LOCTEXT("SourceControl_RevertUnchanged", "Revert Unchanged"), LOCTEXT("SourceControl_Revert_Unchanged_Tooltip", "Reverts unchanged files & changelists"), FSlateIcon(), FUIAction(FExecuteAction::CreateSP(this, &SSourceControlChangelistsWidget::OnRevertUnchanged))); Section.AddMenuEntry("Revert", LOCTEXT("SourceControl_Revert", "Revert Files"), LOCTEXT("SourceControl_Revert_Tooltip", "Reverts all files in the changelist or from the selection"), FSlateIcon(), FUIAction(FExecuteAction::CreateSP(this, &SSourceControlChangelistsWidget::OnRevert))); } if (bHasEmptySelection || bHasSelectedChangelist) { Section.AddSeparator("Changelists"); } // This should appear only if we have no selection if (bHasEmptySelection) { Section.AddMenuEntry("NewChangelist", LOCTEXT("SourceControl_NewChangelist", "New Changelist"), LOCTEXT("SourceControl_NewChangelist_Tooltip", "Creates an empty changelist"), FSlateIcon(), FUIAction(FExecuteAction::CreateSP(this, &SSourceControlChangelistsWidget::OnNewChangelist))); } if (bHasSelectedChangelist) { Section.AddMenuEntry("EditChangelist", LOCTEXT("SourceControl_EditChangelist", "Edit Changelist"), LOCTEXT("SourceControl_Edit_Changelist_Tooltip", "Edit a changelist description"), FSlateIcon(), FUIAction(FExecuteAction::CreateSP(this, &SSourceControlChangelistsWidget::OnEditChangelist))); Section.AddMenuEntry("DeleteChangelist", LOCTEXT("SourceControl_DeleteChangelist", "Delete Empty Changelist"), LOCTEXT("SourceControl_Delete_Changelist_Tooltip", "Deletes an empty changelist"), FSlateIcon(), FUIAction( FExecuteAction::CreateSP(this, &SSourceControlChangelistsWidget::OnDeleteChangelist), FCanExecuteAction::CreateSP(this, &SSourceControlChangelistsWidget::CanDeleteChangelist))); } // Files-only operations if (!bHasEmptySelection && !bHasSelectedChangelist) { Section.AddSeparator("Files"); Section.AddMenuEntry("Locate File", LOCTEXT("SourceControl_LocateFile", "Locate File"), LOCTEXT("SourceControl_LocateFile_Tooltip", "Locate File in Project..."), FSlateIcon(), FUIAction( FExecuteAction::CreateSP(this, &SSourceControlChangelistsWidget::OnLocateFile), FCanExecuteAction::CreateSP(this, &SSourceControlChangelistsWidget::CanLocateFile))); Section.AddMenuEntry("Show History", LOCTEXT("SourceControl_ShowHistory", "Show History"), LOCTEXT("SourceControl_ShowHistory_ToolTip", "Show File History From Selection..."), FSlateIcon(), FUIAction( FExecuteAction::CreateSP(this, &SSourceControlChangelistsWidget::OnShowHistory))); Section.AddMenuEntry("Diff Against Local Version", LOCTEXT("SourceControl_DiffAgainstDepot", "Diff Against Depot"), LOCTEXT("SourceControl_DiffAgainstLocal_Tooltip", "Diff local file against depot revision."), FSlateIcon(), FUIAction( FExecuteAction::CreateSP(this, &SSourceControlChangelistsWidget::OnDiffAgainstDepot), FCanExecuteAction::CreateSP(this, &SSourceControlChangelistsWidget::CanDiffAgainstDepot))); } return ToolMenus->GenerateWidget(Menu); } TSharedRef SSourceControlChangelistsWidget::CreateTreeviewWidget() { return SAssignNew(TreeView, SChangelistTree) .ItemHeight(24.0f) .TreeItemsSource(&ChangelistsNodes) .OnGenerateRow(this, &SSourceControlChangelistsWidget::OnGenerateRow) .OnGetChildren(this, &SSourceControlChangelistsWidget::OnGetChildren) .SelectionMode(ESelectionMode::Multi) .OnContextMenuOpening(this, &SSourceControlChangelistsWidget::OnOpenContextMenu) .HeaderRow ( SNew(SHeaderRow) + SHeaderRow::Column("Change") .DefaultLabel(LOCTEXT("Change", "Change")) .FillWidth(0.2f) + SHeaderRow::Column("Description") .DefaultLabel(LOCTEXT("Description", "Description")) .FillWidth(0.6f) + SHeaderRow::Column("Type") .DefaultLabel(LOCTEXT("Type", "Type")) .FillWidth(0.2f) ); } class SChangelistTableRow : public SMultiColumnTableRow { public: SLATE_BEGIN_ARGS(SChangelistTableRow) : _TreeItemToVisualize() {} SLATE_ARGUMENT(FChangelistTreeItemPtr, TreeItemToVisualize) SLATE_END_ARGS() public: /** * Construct child widgets that comprise this widget. * * @param InArgs Declaration from which to construct this widget. */ void Construct(const FArguments& InArgs, const TSharedRef& InOwner) { TreeItem = static_cast(InArgs._TreeItemToVisualize.Get()); auto Args = FSuperRowType::FArguments(); SMultiColumnTableRow::Construct(Args, InOwner); } // SMultiColumnTableRow overrides virtual TSharedRef GenerateWidgetForColumn(const FName& ColumnName) override { if (ColumnName == TEXT("Change")) { const FSlateBrush* IconBrush = FEditorStyle::GetBrush("SourceControl.Changelist"); return SNew(SHorizontalBox) + SHorizontalBox::Slot() .AutoWidth() .VAlign(VAlign_Center) [ SNew(SExpanderArrow, SharedThis(this)) ] + 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, &SChangelistTableRow::GetChangelistText) ]; } else if (ColumnName == TEXT("Description")) { return SNew(SHorizontalBox) + SHorizontalBox::Slot() .AutoWidth() .Padding(2.0f, 0.0f) .VAlign(VAlign_Center) [ SNew(STextBlock) .Text(this, &SChangelistTableRow::GetChangelistDescriptionText) ]; } else { return SNullWidget::NullWidget; } } FText GetChangelistText() const { return TreeItem->GetDisplayText(); } FText GetChangelistDescriptionText() const { FString DescriptionString = TreeItem->GetDescriptionText().ToString(); DescriptionString.ReplaceInline(TEXT("\n"), TEXT(" ")); DescriptionString.TrimEndInline(); return FText::FromString(DescriptionString); } protected: //~ Begin STableRow Interface. virtual FReply OnDrop(const FGeometry& InGeometry, const FDragDropEvent& InDragDropEvent) override { TSharedPtr Operation = InDragDropEvent.GetOperationAs(); if (Operation.IsValid()) { FSourceControlChangelistPtr Changelist = TreeItem->ChangelistState->GetChangelist(); check(Changelist.IsValid()); TArray Files; Algo::Transform(Operation->Files, Files, [](const FSourceControlStateRef& State) { return State->GetFilename(); }); ISourceControlProvider& SourceControlProvider = ISourceControlModule::Get().GetProvider(); SourceControlProvider.Execute(ISourceControlOperation::Create(), Changelist, Files); } return FReply::Handled(); } //~ End STableRow Interface. private: /** The info about the widget that we are visualizing. */ FChangelistTreeItem* TreeItem; }; class SFileTableRow : public SMultiColumnTableRow { public: SLATE_BEGIN_ARGS(SFileTableRow) : _TreeItemToVisualize() {} SLATE_ARGUMENT(FChangelistTreeItemPtr, TreeItemToVisualize) SLATE_EVENT(FOnDragDetected, OnDragDetected) SLATE_END_ARGS() public: /** * Construct child widgets that comprise this widget. * * @param InArgs Declaration from which to construct this widget. */ void Construct(const FArguments& InArgs, const TSharedRef& InOwner) { TreeItem = static_cast(InArgs._TreeItemToVisualize.Get()); auto Args = FSuperRowType::FArguments() .OnDragDetected(InArgs._OnDragDetected) .ShowSelection(true); FSuperRowType::Construct(Args, InOwner); } // SMultiColumnTableRow overrides virtual TSharedRef GenerateWidgetForColumn(const FName& ColumnName) override { if (ColumnName == TEXT("Change")) // eq. to name { return SNew(SHorizontalBox) // Icon + SHorizontalBox::Slot() .AutoWidth() .Padding(40, 0, 4, 0) [ GetSCCFileWidget(TreeItem->FileState) ] + SHorizontalBox::Slot() .AutoWidth() .VAlign(VAlign_Center) [ SNew(STextBlock) .Text(this, &SFileTableRow::GetDisplayName) ]; } else if (ColumnName == TEXT("Description")) // eq. to path { return SNew(STextBlock) .Text(this, &SFileTableRow::GetDisplayPath); } else if (ColumnName == TEXT("Type")) { return SNew(STextBlock) .Text(this, &SFileTableRow::GetDisplayType) .ColorAndOpacity(this, &SFileTableRow::GetDisplayColor); } else { return SNullWidget::NullWidget; } } FText GetDisplayName() const { return TreeItem->GetDisplayName(); } FText GetDisplayPath() const { return TreeItem->GetDisplayPath(); } FText GetDisplayType() const { return TreeItem->GetDisplayType(); } FSlateColor GetDisplayColor() const { return TreeItem->GetDisplayColor(); } protected: //~ Begin STableRow Interface. virtual void OnDragEnter(FGeometry const& InGeometry, FDragDropEvent const& InDragDropEvent) override { TSharedPtr DragOperation = InDragDropEvent.GetOperation(); DragOperation->SetCursorOverride(EMouseCursor::SlashedCircle); } virtual void OnDragLeave(FDragDropEvent const& InDragDropEvent) override { TSharedPtr DragOperation = InDragDropEvent.GetOperation(); DragOperation->SetCursorOverride(EMouseCursor::None); } //~ End STableRow Interface. private: /** The info about the widget that we are visualizing. */ FFileTreeItem* TreeItem; }; TSharedRef SSourceControlChangelistsWidget::OnGenerateRow(FChangelistTreeItemPtr InTreeItem, const TSharedRef& OwnerTable) { switch (InTreeItem->GetTreeItemType()) { case IChangelistTreeItem::Changelist: return SNew(SChangelistTableRow, OwnerTable) .TreeItemToVisualize(InTreeItem); case IChangelistTreeItem::File: return SNew(SFileTableRow, OwnerTable) .TreeItemToVisualize(InTreeItem) .OnDragDetected(this, &SSourceControlChangelistsWidget::OnFilesDragged); default: check(false); }; return SNew(STableRow>, OwnerTable); } FReply SSourceControlChangelistsWidget::OnFilesDragged(const FGeometry& InGeometry, const FPointerEvent& InMouseEvent) { if (InMouseEvent.IsMouseButtonDown(EKeys::LeftMouseButton) && !TreeView->GetSelectedItems().IsEmpty()) { TSharedRef Operation = MakeShareable(new FSCCFileDragDropOp()); Algo::Transform(TreeView->GetSelectedItems(), Operation->Files, [](FChangelistTreeItemPtr InTreeItem) { check(InTreeItem->GetTreeItemType() == IChangelistTreeItem::File); return static_cast(InTreeItem.Get())->FileState; }); Operation->Construct(); return FReply::Handled().BeginDragDrop(Operation); } return FReply::Unhandled(); } void SSourceControlChangelistsWidget::OnGetChildren(FChangelistTreeItemPtr InParent, TArray& OutChildren) { for (auto& Child : InParent->GetChildren()) { // Should never have bogus entries in this list check(Child.IsValid()); OutChildren.Add(Child); } } #undef LOCTEXT_NAMESPACE