Files
UnrealEngineUWP/Engine/Source/Editor/SourceControlWindows/Private/SSourceControlChangelists.cpp
sebastien lussier ef589f0b39 Minor UX improvements to the Source Control Changelists window
#rb none

#ROBOMERGE-SOURCE: CL 15554968 in //UE5/Release-5.0-EarlyAccess/...
#ROBOMERGE-BOT: STARSHIP (Release-5.0-EarlyAccess -> Main) (v771-15082668)

[CL 15554971 by sebastien lussier in ue5-main branch]
2021-02-28 23:55:37 -04:00

1256 lines
40 KiB
C++

// 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/Input/SButton.h"
#include "Widgets/Layout/SBorder.h"
#include "Widgets/Layout/SBox.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 "SourceControlHelpers.h"
#include "AssetToolsModule.h"
#include "ContentBrowserModule.h"
#include "IContentBrowserSingleton.h"
#include "Misc/MessageDialog.h"
#include "Algo/AnyOf.h"
#include "SSourceControlSubmit.h"
#include "Framework/Application/SlateApplication.h"
#define LOCTEXT_NAMESPACE "SourceControlChangelist"
//////////////////////////////
struct FSCCFileDragDropOp : public FDragDropOperation
{
DRAG_DROP_OPERATOR_TYPE(FSCCFileDragDropOp, FDragDropOperation);
using FDragDropOperation::Construct;
virtual TSharedPtr<SWidget> GetDefaultDecorator() const override
{
return SSourceControlCommon::GetSCCFileWidget(Files[0]);
}
TArray<FSourceControlStateRef> Files;
};
//////////////////////////////
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(SVerticalBox)
+ SVerticalBox::Slot()
.AutoHeight()
[
SNew(SBorder)
.BorderImage(FEditorStyle::GetBrush("ToolPanel.GroupBorder"))
.Padding(4)
[
SNew(SHorizontalBox)
+SHorizontalBox::Slot()
.HAlign(HAlign_Left)
.VAlign(VAlign_Center)
.AutoWidth()
[
MakeToolBar()
]
]
]
+ SVerticalBox::Slot()
[
SNew(SScrollBorder, TreeView.ToSharedRef())
.Visibility(TAttribute<EVisibility>::Create(TAttribute<EVisibility>::FGetter::CreateLambda([]()->EVisibility { return ISourceControlModule::Get().IsEnabled() ? EVisibility::Visible : EVisibility::Hidden; })))
[
TreeView.ToSharedRef()
]
]
];
bShouldRefresh = true;
}
TSharedRef<SWidget> SSourceControlChangelistsWidget::MakeToolBar()
{
FSlimHorizontalToolBarBuilder ToolBarBuilder(nullptr, FMultiBoxCustomization::None);
ToolBarBuilder.AddToolBarButton(
FUIAction(
FExecuteAction::CreateLambda([this]() {
RequestRefresh();
})),
NAME_None,
LOCTEXT("SourceControl_RefreshButton", "Refresh"),
LOCTEXT("SourceControl_RefreshButton_Tooltip", "Refreshes changelists from source control provider."),
FSlateIcon(FEditorStyle::GetStyleSetName(), "SourceControl.Actions.Refresh"));
return ToolBarBuilder.MakeWidget();
}
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<FUpdatePendingChangelistsStatus, ESPMode::ThreadSafe> UpdatePendingChangelistsOperation = ISourceControlOperation::Create<FUpdatePendingChangelistsStatus>();
UpdatePendingChangelistsOperation->SetUpdateAllChangelists(true);
UpdatePendingChangelistsOperation->SetUpdateFilesStates(true);
UpdatePendingChangelistsOperation->SetUpdateShelvedFilesStates(true);
ISourceControlProvider& SourceControlProvider = ISourceControlModule::Get().GetProvider();
SourceControlProvider.Execute(UpdatePendingChangelistsOperation, EConcurrency::Asynchronous);
}
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())
{
TMap<FSourceControlChangelistStateRef, ExpandedState> ExpandedStates;
SaveExpandedState(ExpandedStates);
ISourceControlProvider& SourceControlProvider = ISourceControlModule::Get().GetProvider();
TArray<FSourceControlChangelistRef> Changelists = SourceControlProvider.GetChangelists(EStateCacheUsage::Use);
TArray<FSourceControlChangelistStateRef> 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);
}
if (ChangelistState->GetShelvedFilesStates().Num() > 0)
{
FChangelistTreeItemRef ShelvedChangelistTreeItem = MakeShareable(new FShelvedChangelistTreeItem());
ChangelistTreeItem->AddChild(ShelvedChangelistTreeItem);
for (FSourceControlStateRef ShelvedFileRef : ChangelistState->GetShelvedFilesStates())
{
FChangelistTreeItemRef ShelvedFileTreeItem = MakeShareable(new FShelvedFileTreeItem(ShelvedFileRef));
ShelvedChangelistTreeItem->AddChild(ShelvedFileTreeItem);
}
}
ChangelistsNodes.Add(ChangelistTreeItem);
}
RestoreExpandedState(ExpandedStates);
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<FChangelistTreeItemPtr> SelectedItems = TreeView->GetSelectedItems();
if (SelectedItems.Num() != 1 || SelectedItems[0]->GetTreeItemType() != IChangelistTreeItem::Changelist)
{
return nullptr;
}
else
{
return StaticCastSharedPtr<FChangelistTreeItem>(SelectedItems[0])->ChangelistState;
}
}
FSourceControlChangelistPtr SSourceControlChangelistsWidget::GetCurrentChangelist()
{
FSourceControlChangelistStatePtr ChangelistState = GetCurrentChangelistState();
return ChangelistState ? (FSourceControlChangelistPtr)(ChangelistState->GetChangelist()) : nullptr;
}
FSourceControlChangelistStatePtr SSourceControlChangelistsWidget::GetChangelistStateFromSelection()
{
if (!TreeView)
{
return nullptr;
}
TArray<FChangelistTreeItemPtr> SelectedItems = TreeView->GetSelectedItems();
if (SelectedItems.Num() == 0 || SelectedItems[0]->GetTreeItemType() == IChangelistTreeItem::Invalid)
{
return nullptr;
}
FChangelistTreeItemPtr Item = SelectedItems[0];
while (Item && Item->GetTreeItemType() != IChangelistTreeItem::Invalid)
{
if (Item->GetTreeItemType() == IChangelistTreeItem::Changelist)
return StaticCastSharedPtr<FChangelistTreeItem>(Item)->ChangelistState;
else
Item = Item->GetParent();
}
return nullptr;
}
FSourceControlChangelistPtr SSourceControlChangelistsWidget::GetChangelistFromSelection()
{
FSourceControlChangelistStatePtr ChangelistState = GetChangelistStateFromSelection();
return ChangelistState ? (FSourceControlChangelistPtr)(ChangelistState->GetChangelist()) : nullptr;
}
TArray<FString> SSourceControlChangelistsWidget::GetSelectedFiles()
{
TArray<FChangelistTreeItemPtr> SelectedItems = TreeView->GetSelectedItems();
if (SelectedItems.Num() == 0 || SelectedItems[0]->GetTreeItemType() != IChangelistTreeItem::File)
{
return TArray<FString>();
}
else
{
TArray<FString> Files;
for (FChangelistTreeItemPtr Item : SelectedItems)
{
Files.Add(StaticCastSharedPtr<FFileTreeItem>(Item)->FileState->GetFilename());
}
return Files;
}
}
TArray<FString> SSourceControlChangelistsWidget::GetSelectedShelvedFiles()
{
TArray<FString> ShelvedFiles;
TArray<FChangelistTreeItemPtr> SelectedItems = TreeView->GetSelectedItems();
if (SelectedItems.Num() > 0)
{
if (SelectedItems[0]->GetTreeItemType() == IChangelistTreeItem::ShelvedChangelist)
{
check(SelectedItems.Num() == 1);
const TArray<FChangelistTreeItemPtr>& ShelvedChildren = SelectedItems[0]->GetChildren();
for (FChangelistTreeItemPtr Item : ShelvedChildren)
{
ShelvedFiles.Add(StaticCastSharedPtr<FShelvedFileTreeItem>(Item)->FileState->GetFilename());
}
}
else if (SelectedItems[0]->GetTreeItemType() == IChangelistTreeItem::ShelvedFile)
{
for (FChangelistTreeItemPtr Item : SelectedItems)
{
ShelvedFiles.Add(StaticCastSharedPtr<FShelvedFileTreeItem>(Item)->FileState->GetFilename());
}
}
}
return ShelvedFiles;
}
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<FNewChangelist>();
NewChangelistOperation->SetDescription(ChangelistDescription);
SourceControlProvider.Execute(NewChangelistOperation);
}
void SSourceControlChangelistsWidget::OnDeleteChangelist()
{
if (GetCurrentChangelist() == nullptr)
{
return;
}
ISourceControlProvider& SourceControlProvider = ISourceControlModule::Get().GetProvider();
SourceControlProvider.Execute(ISourceControlOperation::Create<FDeleteChangelist>(), GetCurrentChangelist());
}
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<FEditChangelist>();
EditChangelistOperation->SetDescription(NewChangelistDescription);
ISourceControlProvider& SourceControlProvider = ISourceControlModule::Get().GetProvider();
SourceControlProvider.Execute(EditChangelistOperation, ChangelistState->GetChangelist());
}
void SSourceControlChangelistsWidget::OnRevertUnchanged()
{
ISourceControlProvider& SourceControlProvider = ISourceControlModule::Get().GetProvider();
auto RevertUnchangedOperation = ISourceControlOperation::Create<FRevertUnchanged>();
SourceControlProvider.Execute(RevertUnchangedOperation, GetChangelistFromSelection(), GetSelectedFiles());
}
bool SSourceControlChangelistsWidget::CanRevertUnchanged()
{
return GetSelectedFiles().Num() > 0 || (GetCurrentChangelistState() && GetCurrentChangelistState()->GetFilesStates().Num() > 0);
}
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<FRevert>();
SourceControlProvider.Execute(RevertOperation, GetChangelistFromSelection(), GetSelectedFiles());
}
bool SSourceControlChangelistsWidget::CanRevert()
{
return GetSelectedFiles().Num() > 0 || (GetCurrentChangelistState() && GetCurrentChangelistState()->GetFilesStates().Num() > 0);
}
void SSourceControlChangelistsWidget::OnShelve()
{
FSourceControlChangelistStatePtr CurrentChangelist = GetChangelistStateFromSelection();
if (!CurrentChangelist)
{
return;
}
FText ChangelistDescription = CurrentChangelist->GetDescriptionText();
if (ChangelistDescription.IsEmptyOrWhitespace())
{
bool bOk = GetChangelistDescription(
nullptr,
LOCTEXT("SourceControl.Changelist.NewShelve", "Shelving files..."),
LOCTEXT("SourceControl.Changelist.NewShelve.Label", "Enter a description for the changelist holding the shelve:"),
ChangelistDescription);
if (!bOk)
{
// User cancelled entering a changelist description; abort shelve
return;
}
}
ISourceControlProvider& SourceControlProvider = ISourceControlModule::Get().GetProvider();
auto ShelveOperation = ISourceControlOperation::Create<FShelve>();
ShelveOperation->SetDescription(ChangelistDescription);
SourceControlProvider.Execute(ShelveOperation, CurrentChangelist->GetChangelist(), GetSelectedFiles());
}
void SSourceControlChangelistsWidget::OnUnshelve()
{
ISourceControlProvider& SourceControlProvider = ISourceControlModule::Get().GetProvider();
auto UnshelveOperation = ISourceControlOperation::Create<FUnshelve>();
SourceControlProvider.Execute(UnshelveOperation, GetChangelistFromSelection(), GetSelectedShelvedFiles());
}
void SSourceControlChangelistsWidget::OnDeleteShelvedFiles()
{
ISourceControlProvider& SourceControlProvider = ISourceControlModule::Get().GetProvider();
auto DeleteShelvedOperation = ISourceControlOperation::Create<FDeleteShelved>();
SourceControlProvider.Execute(DeleteShelvedOperation, GetChangelistFromSelection(), GetSelectedShelvedFiles());
}
static void GetChangelistValidationResult(FSourceControlChangelistPtr InChangelist, FString& OutValidationText, FName& OutValidationIcon)
{
FSourceControlPreSubmitDataValidationDelegate ValidationDelegate = ISourceControlModule::Get().GetRegisteredPreSubmitDataValidation();
EDataValidationResult ValidationResult = EDataValidationResult::NotValidated;
TArray<FText> ValidationErrors;
TArray<FText> ValidationWarnings;
if (ValidationDelegate.ExecuteIfBound(InChangelist, ValidationResult, ValidationErrors, ValidationWarnings))
{
if (ValidationResult == EDataValidationResult::Invalid || ValidationErrors.Num() > 0)
{
OutValidationIcon = "MessageLog.Error";
OutValidationText = LOCTEXT("SourceControl.Submit.ChangelistValidationError", "Changelist validation failed!").ToString();
}
else if (ValidationResult == EDataValidationResult::NotValidated || ValidationWarnings.Num() > 0)
{
OutValidationIcon = "MessageLog.Warning";
OutValidationText = LOCTEXT("SourceControl.Submit.ChangelistValidationWarning", "Changelist validation has warnings!").ToString();
}
else
{
OutValidationIcon = "MessageLog.Note";
OutValidationText = LOCTEXT("SourceControl.Submit.ChangelistValidationSuccess", "Changelist validation succesful!").ToString();
}
int32 NumLinesDisplayed = 0;
auto AppendInfo = [&OutValidationText, &NumLinesDisplayed](const TArray<FText>& Info, const FString& InfoType) {
const int32 MaxNumLinesDisplayed = 5;
if (Info.Num() > 0)
{
OutValidationText += LINE_TERMINATOR;
OutValidationText += FString::Printf(TEXT("Encountered %d %s:"), Info.Num(), *InfoType);
for (const FText& Line : Info)
{
if (NumLinesDisplayed >= MaxNumLinesDisplayed)
{
break;
}
OutValidationText += LINE_TERMINATOR;
OutValidationText += Line.ToString();
++NumLinesDisplayed;
}
}
};
AppendInfo(ValidationErrors, TEXT("errors"));
AppendInfo(ValidationWarnings, TEXT("warnings"));
}
}
void SSourceControlChangelistsWidget::OnSubmitChangelist()
{
FSourceControlChangelistStatePtr ChangelistState = GetCurrentChangelistState();
if (!ChangelistState)
{
return;
}
FString ChangelistValidationText;
FName ChangelistValidationIconName;
GetChangelistValidationResult(ChangelistState->GetChangelist(), ChangelistValidationText, ChangelistValidationIconName);
// Build list of states for the dialog
FText ChangelistDescription = ChangelistState->GetDescriptionText();
const bool bAskForChangelistDescription = (ChangelistDescription.IsEmptyOrWhitespace());
TSharedRef<SWindow> NewWindow = SNew(SWindow)
.Title(NSLOCTEXT("SourceControl.ConfirmSubmit", "Title", "Confirm changelist submit"))
.SizingRule(ESizingRule::UserSized)
.ClientSize(FVector2D(600, 400))
.SupportsMaximize(true)
.SupportsMinimize(false);
TSharedRef<SSourceControlSubmitWidget> SourceControlWidget =
SNew(SSourceControlSubmitWidget)
.ParentWindow(NewWindow)
.Items(ChangelistState->GetFilesStates())
.Description(ChangelistDescription)
.ChangeValidationDescription(ChangelistValidationText)
.ChangeValidationIcon(ChangelistValidationIconName)
.AllowDescriptionChange(bAskForChangelistDescription)
.AllowUncheckFiles(false)
.AllowKeepCheckedOut(false);
NewWindow->SetContent(
SourceControlWidget
);
FSlateApplication::Get().AddModalWindow(NewWindow, NULL);
if (SourceControlWidget->GetResult() == ESubmitResults::SUBMIT_ACCEPTED)
{
ISourceControlProvider& SourceControlProvider = ISourceControlModule::Get().GetProvider();
auto SubmitChangelistOperation = ISourceControlOperation::Create<FCheckIn>();
// Replace description only if there was none
if (bAskForChangelistDescription)
{
FChangeListDescription Description;
SourceControlWidget->FillChangeListDescription(Description);
SubmitChangelistOperation->SetDescription(Description.Description);
}
SourceControlProvider.Execute(SubmitChangelistOperation, ChangelistState->GetChangelist());
}
}
bool SSourceControlChangelistsWidget::CanSubmitChangelist()
{
FSourceControlChangelistStatePtr Changelist = GetCurrentChangelistState();
return Changelist != nullptr && Changelist->GetFilesStates().Num() > 0 && Changelist->GetShelvedFilesStates().Num() == 0;
}
void SSourceControlChangelistsWidget::OnLocateFile()
{
TArray<FAssetData> AssetsToSync;
TArray<FChangelistTreeItemPtr> SelectedItems = TreeView->GetSelectedItems();
for (const FChangelistTreeItemPtr& SelectedItem : SelectedItems)
{
if (SelectedItem->GetTreeItemType() == IChangelistTreeItem::File)
{
AssetsToSync.Append(StaticCastSharedPtr<FFileTreeItem>(SelectedItem)->GetAssetData());
}
}
if (AssetsToSync.Num() > 0)
{
FContentBrowserModule& ContentBrowserModule = FModuleManager::Get().LoadModuleChecked<FContentBrowserModule>("ContentBrowser");
ContentBrowserModule.Get().SyncBrowserToAssets(AssetsToSync, true);
}
}
bool SSourceControlChangelistsWidget::CanLocateFile()
{
return GetSelectedFiles().Num() > 0;
}
void SSourceControlChangelistsWidget::OnShowHistory()
{
TArray<FString> SelectedFiles = GetSelectedFiles();
if (SelectedFiles.Num() > 0)
{
FSourceControlWindows::DisplayRevisionHistory(SelectedFiles);
}
}
void SSourceControlChangelistsWidget::OnDiffAgainstDepot()
{
TArray<FString> SelectedFiles = GetSelectedFiles();
if (SelectedFiles.Num() > 0)
{
FSourceControlWindows::DiffAgainstWorkspace(SelectedFiles[0]);
}
}
bool SSourceControlChangelistsWidget::CanDiffAgainstDepot()
{
return GetSelectedFiles().Num() == 1;
}
void SSourceControlChangelistsWidget::OnDiffAgainstWorkspace()
{
if (GetSelectedShelvedFiles().Num() > 0)
{
FSourceControlStateRef FileState = StaticCastSharedPtr<FShelvedFileTreeItem>(TreeView->GetSelectedItems()[0])->FileState;
FSourceControlWindows::DiffAgainstShelvedFile(FileState);
}
}
bool SSourceControlChangelistsWidget::CanDiffAgainstWorkspace()
{
return GetSelectedShelvedFiles().Num() == 1;
}
TSharedPtr<SWidget> 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 bHasSelectedFiles = (GetSelectedFiles().Num() > 0);
bool bHasSelectedShelvedFiles = (GetSelectedShelvedFiles().Num() > 0);
bool bHasEmptySelection = (!bHasSelectedChangelist && !bHasSelectedFiles && !bHasSelectedShelvedFiles);
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 (bHasSelectedChangelist || bHasSelectedFiles)
{
Section.AddMenuEntry("RevertUnchanged", LOCTEXT("SourceControl_RevertUnchanged", "Revert Unchanged"), LOCTEXT("SourceControl_Revert_Unchanged_Tooltip", "Reverts unchanged files & changelists"), FSlateIcon(),
FUIAction(
FExecuteAction::CreateSP(this, &SSourceControlChangelistsWidget::OnRevertUnchanged),
FCanExecuteAction::CreateSP(this, &SSourceControlChangelistsWidget::CanRevertUnchanged)));
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),
FCanExecuteAction::CreateSP(this, &SSourceControlChangelistsWidget::CanRevert)));
}
if(bHasSelectedFiles || bHasSelectedShelvedFiles || (bHasSelectedChangelist && (GetCurrentChangelistState()->GetFilesStates().Num() > 0 || GetCurrentChangelistState()->GetShelvedFilesStates().Num() > 0)))
{
Section.AddSeparator("ShelveSeparator");
}
if(bHasSelectedFiles || (bHasSelectedChangelist && GetCurrentChangelistState()->GetFilesStates().Num() > 0))
{
Section.AddMenuEntry("Shelve", LOCTEXT("SourceControl_Shelve", "Shelve Files"), LOCTEXT("SourceControl_Shelve_Tooltip", "Shelves the changelist or the selected files"), FSlateIcon(),
FUIAction(FExecuteAction::CreateSP(this, &SSourceControlChangelistsWidget::OnShelve)));
}
if (bHasSelectedShelvedFiles || (bHasSelectedChangelist && GetCurrentChangelistState()->GetShelvedFilesStates().Num() > 0))
{
Section.AddMenuEntry("Unshelve", LOCTEXT("SourceControl_Unshelve", "Unshelve Files"), LOCTEXT("SourceControl_Unshelve_Tooltip", "Unshelve selected files or changelist"), FSlateIcon(),
FUIAction(FExecuteAction::CreateSP(this, &SSourceControlChangelistsWidget::OnUnshelve)));
Section.AddMenuEntry("DeleteShelved", LOCTEXT("SourceControl_DeleteShelved", "Delete Shelved Files"), LOCTEXT("SourceControl_DeleteShelved_Tooltip", "Delete selected shelved files or all from changelist"), FSlateIcon(),
FUIAction(FExecuteAction::CreateSP(this, &SSourceControlChangelistsWidget::OnDeleteShelvedFiles)));
}
// Shelved files-only operations
if (bHasSelectedShelvedFiles)
{
// Diff against workspace
Section.AddMenuEntry("DiffAgainstWorkspace", LOCTEXT("SourceControl_DiffAgainstWorkspace", "Diff Against Workspace Files..."), LOCTEXT("SourceControl_DiffAgainstWorkspace_Tooltip", "Diff shelved file against the (local) workspace file"), FSlateIcon(),
FUIAction(
FExecuteAction::CreateSP(this, &SSourceControlChangelistsWidget::OnDiffAgainstWorkspace),
FCanExecuteAction::CreateSP(this, &SSourceControlChangelistsWidget::CanDiffAgainstWorkspace)));
}
if (bHasEmptySelection || bHasSelectedChangelist)
{
Section.AddSeparator("ChangelistsSeparator");
}
// 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(bHasSelectedFiles)
{
Section.AddSeparator("FilesSeparator");
Section.AddMenuEntry("LocateFile", 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("ShowHistory", LOCTEXT("SourceControl_ShowHistory", "Show History..."), LOCTEXT("SourceControl_ShowHistory_ToolTip", "Show File History From Selection..."), FSlateIcon(),
FUIAction(
FExecuteAction::CreateSP(this, &SSourceControlChangelistsWidget::OnShowHistory)));
Section.AddMenuEntry("DiffAgainstLocalVersion", 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<SChangelistTree> 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<FChangelistTreeItemPtr>
{
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<STableViewBase>& InOwner)
{
TreeItem = static_cast<FChangelistTreeItem*>(InArgs._TreeItemToVisualize.Get());
auto Args = FSuperRowType::FArguments();
SMultiColumnTableRow<FChangelistTreeItemPtr>::Construct(Args, InOwner);
}
// SMultiColumnTableRow overrides
virtual TSharedRef<SWidget> 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();
// Here we'll both remove \r\n (when edited from the dialog) and \n (when we get it from the SCC)
DescriptionString.ReplaceInline(TEXT("\r"), TEXT(""));
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<FSCCFileDragDropOp> Operation = InDragDropEvent.GetOperationAs<FSCCFileDragDropOp>();
if (Operation.IsValid())
{
FSourceControlChangelistPtr Changelist = TreeItem->ChangelistState->GetChangelist();
check(Changelist.IsValid());
TArray<FString> Files;
Algo::Transform(Operation->Files, Files, [](const FSourceControlStateRef& State) { return State->GetFilename(); });
ISourceControlProvider& SourceControlProvider = ISourceControlModule::Get().GetProvider();
SourceControlProvider.Execute(ISourceControlOperation::Create<FMoveToChangelist>(), Changelist, Files);
}
return FReply::Handled();
}
//~ End STableRow Interface.
private:
/** The info about the widget that we are visualizing. */
FChangelistTreeItem* TreeItem;
};
class SFileTableRow : public SMultiColumnTableRow<FChangelistTreeItemPtr>
{
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<STableViewBase>& InOwner)
{
TreeItem = static_cast<FFileTreeItem*>(InArgs._TreeItemToVisualize.Get());
auto Args = FSuperRowType::FArguments()
.OnDragDetected(InArgs._OnDragDetected)
.ShowSelection(true);
FSuperRowType::Construct(Args, InOwner);
}
// SMultiColumnTableRow overrides
virtual TSharedRef<SWidget> GenerateWidgetForColumn(const FName& ColumnName) override
{
if (ColumnName == TEXT("Change")) // eq. to name
{
const int32 LeftOffset = (TreeItem->IsShelved() ? 60 : 40);
return SNew(SHorizontalBox)
// Icon
+ SHorizontalBox::Slot()
.AutoWidth()
.Padding(LeftOffset, 0, 4, 0)
[
SSourceControlCommon::GetSCCFileWidget(TreeItem->FileState, TreeItem->IsShelved())
]
+ 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)
.ToolTipText(this, &SFileTableRow::GetFilename);
}
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->GetAssetName();
}
FText GetFilename() const
{
return TreeItem->GetFileName();
}
FText GetDisplayPath() const
{
return TreeItem->GetAssetPath();
}
FText GetDisplayType() const
{
return TreeItem->GetAssetType();
}
FSlateColor GetDisplayColor() const
{
return TreeItem->GetAssetTypeColor();
}
protected:
//~ Begin STableRow Interface.
virtual void OnDragEnter(FGeometry const& InGeometry, FDragDropEvent const& InDragDropEvent) override
{
TSharedPtr<FDragDropOperation> DragOperation = InDragDropEvent.GetOperation();
DragOperation->SetCursorOverride(EMouseCursor::SlashedCircle);
}
virtual void OnDragLeave(FDragDropEvent const& InDragDropEvent) override
{
TSharedPtr<FDragDropOperation> DragOperation = InDragDropEvent.GetOperation();
DragOperation->SetCursorOverride(EMouseCursor::None);
}
//~ End STableRow Interface.
private:
/** The info about the widget that we are visualizing. */
FFileTreeItem* TreeItem;
};
class SShelvedChangelistTableRow : public SMultiColumnTableRow<FChangelistTreeItemPtr>
{
public:
SLATE_BEGIN_ARGS(SShelvedChangelistTableRow)
: _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<STableViewBase>& InOwner)
{
TreeItem = static_cast<FShelvedChangelistTreeItem*>(InArgs._TreeItemToVisualize.Get());
auto Args = FSuperRowType::FArguments();
SMultiColumnTableRow<FChangelistTreeItemPtr>::Construct(Args, InOwner);
}
// SMultiColumnTableRow overrides
virtual TSharedRef<SWidget> GenerateWidgetForColumn(const FName& ColumnName) override
{
if (ColumnName == TEXT("Change"))
{
return SNew(SHorizontalBox)
+ SHorizontalBox::Slot()
.AutoWidth()
.VAlign(VAlign_Center)
.Padding(5, 0, 4, 0)
[
SNew(SExpanderArrow, SharedThis(this))
]
+ SHorizontalBox::Slot()
.AutoWidth()
.VAlign(VAlign_Center)
.Padding(5, 0, 0, 0)
[
SNew(SImage)
.Image(FEditorStyle::GetBrush("SourceControl.ShelvedChangelist"))
]
+ SHorizontalBox::Slot()
.Padding(2.0f, 0.0f)
.VAlign(VAlign_Center)
[
SNew(STextBlock)
.Text(this, &SShelvedChangelistTableRow::GetText)
];
}
else
{
return SNullWidget::NullWidget;
}
}
protected:
FText GetText() const
{
return TreeItem->GetDisplayText();
}
private:
/** The info about the widget that we are visualizing. */
FShelvedChangelistTreeItem* TreeItem;
};
TSharedRef<ITableRow> SSourceControlChangelistsWidget::OnGenerateRow(FChangelistTreeItemPtr InTreeItem, const TSharedRef<STableViewBase>& 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);
case IChangelistTreeItem::ShelvedChangelist:
return SNew(SShelvedChangelistTableRow, OwnerTable)
.TreeItemToVisualize(InTreeItem);
case IChangelistTreeItem::ShelvedFile:
return SNew(SFileTableRow, OwnerTable)
.TreeItemToVisualize(InTreeItem);
default:
check(false);
};
return SNew(STableRow<TSharedPtr<FString>>, OwnerTable);
}
FReply SSourceControlChangelistsWidget::OnFilesDragged(const FGeometry& InGeometry, const FPointerEvent& InMouseEvent)
{
if (InMouseEvent.IsMouseButtonDown(EKeys::LeftMouseButton) && !TreeView->GetSelectedItems().IsEmpty())
{
TSharedRef<FSCCFileDragDropOp> Operation = MakeShareable(new FSCCFileDragDropOp());
Algo::Transform(TreeView->GetSelectedItems(), Operation->Files, [](FChangelistTreeItemPtr InTreeItem) { check(InTreeItem->GetTreeItemType() == IChangelistTreeItem::File); return static_cast<FFileTreeItem*>(InTreeItem.Get())->FileState; });
Operation->Construct();
return FReply::Handled().BeginDragDrop(Operation);
}
return FReply::Unhandled();
}
void SSourceControlChangelistsWidget::OnGetChildren(FChangelistTreeItemPtr InParent, TArray<FChangelistTreeItemPtr>& OutChildren)
{
for (auto& Child : InParent->GetChildren())
{
// Should never have bogus entries in this list
check(Child.IsValid());
OutChildren.Add(Child);
}
}
void SSourceControlChangelistsWidget::SaveExpandedState(TMap<FSourceControlChangelistStateRef, ExpandedState>& ExpandedStates) const
{
for (FChangelistTreeItemPtr Root : ChangelistsNodes)
{
if (Root->GetTreeItemType() != IChangelistTreeItem::Changelist)
{
continue;
}
bool bChangelistExpanded = TreeView->IsItemExpanded(Root);
bool bShelveExpanded = false;
for (FChangelistTreeItemPtr Child : Root->GetChildren())
{
if (Child->GetTreeItemType() == IChangelistTreeItem::ShelvedChangelist)
{
bShelveExpanded = TreeView->IsItemExpanded(Child);
break;
}
}
ExpandedState State;
State.bChangelistExpanded = bChangelistExpanded;
State.bShelveExpanded = bShelveExpanded;
ExpandedStates.Add(StaticCastSharedPtr<FChangelistTreeItem>(Root)->ChangelistState, State);
}
}
void SSourceControlChangelistsWidget::RestoreExpandedState(const TMap<FSourceControlChangelistStateRef, ExpandedState>& ExpandedStates)
{
for (FChangelistTreeItemPtr Root : ChangelistsNodes)
{
if (Root->GetTreeItemType() != IChangelistTreeItem::Changelist)
{
continue;
}
FSourceControlChangelistStateRef ChangelistState = StaticCastSharedPtr<FChangelistTreeItem>(Root)->ChangelistState;
const ExpandedState* State = ExpandedStates.Find(ChangelistState);
if (!State)
{
continue;
}
TreeView->SetItemExpansion(Root, State->bChangelistExpanded);
for (FChangelistTreeItemPtr Child : Root->GetChildren())
{
if (Child->GetTreeItemType() == IChangelistTreeItem::ShelvedChangelist)
{
TreeView->SetItemExpansion(Child, State->bShelveExpanded);
break;
}
}
}
}
#undef LOCTEXT_NAMESPACE