diff --git a/Source/PlasticSourceControl/Private/PlasticSourceControlChangeset.h b/Source/PlasticSourceControl/Private/PlasticSourceControlChangeset.h index 409a4aa5..d16fa966 100644 --- a/Source/PlasticSourceControl/Private/PlasticSourceControlChangeset.h +++ b/Source/PlasticSourceControl/Private/PlasticSourceControlChangeset.h @@ -15,7 +15,7 @@ class FPlasticSourceControlChangeset FString Comment; FString Branch; // Note: array of file States, each with one Revision for Diffing (like for Files and ShelvedFiles in FPlasticSourceControlChangelist) - TArray> Files; + TArray Files; void PopulateSearchString(TArray& OutStrings) const { diff --git a/Source/PlasticSourceControl/Private/PlasticSourceControlOperations.cpp b/Source/PlasticSourceControl/Private/PlasticSourceControlOperations.cpp index ed3dc4fb..eea44e9a 100644 --- a/Source/PlasticSourceControl/Private/PlasticSourceControlOperations.cpp +++ b/Source/PlasticSourceControl/Private/PlasticSourceControlOperations.cpp @@ -58,6 +58,7 @@ void IPlasticSourceControlWorker::RegisterWorkers(FPlasticSourceControlProvider& PlasticSourceControlProvider.RegisterWorker("RenameBranch", FGetPlasticSourceControlWorker::CreateStatic(&InstantiateWorker)); PlasticSourceControlProvider.RegisterWorker("DeleteBranches", FGetPlasticSourceControlWorker::CreateStatic(&InstantiateWorker)); PlasticSourceControlProvider.RegisterWorker("GetChangesets", FGetPlasticSourceControlWorker::CreateStatic(&InstantiateWorker)); + PlasticSourceControlProvider.RegisterWorker("GetChangesetFiles", FGetPlasticSourceControlWorker::CreateStatic(&InstantiateWorker)); PlasticSourceControlProvider.RegisterWorker("MakeWorkspace", FGetPlasticSourceControlWorker::CreateStatic(&InstantiateWorker)); PlasticSourceControlProvider.RegisterWorker("Sync", FGetPlasticSourceControlWorker::CreateStatic(&InstantiateWorker)); PlasticSourceControlProvider.RegisterWorker("SyncAll", FGetPlasticSourceControlWorker::CreateStatic(&InstantiateWorker)); @@ -241,6 +242,17 @@ FText FPlasticGetChangesets::GetInProgressString() const return LOCTEXT("SourceControl_GetChangesets", "Getting the list of changesets..."); } +FName FPlasticGetChangesetFiles::GetName() const +{ + return "GetChangesetFiles"; +} + +FText FPlasticGetChangesetFiles::GetInProgressString() const +{ + return FText::Format(LOCTEXT("SourceControl_GetChangesetFiles", "Getting the list of files in changeset {0}..."), FText::AsNumber(Changeset->ChangesetId)); +} + + static bool AreAllFiles(const TArray& InFiles) { for (const FString& File : InFiles) @@ -1396,6 +1408,41 @@ bool FPlasticGetChangesetsWorker::UpdateStates() } + +FName FPlasticGetChangesetFilesWorker::GetName() const +{ + return "GetChangesetFiles"; +} + +bool FPlasticGetChangesetFilesWorker::Execute(FPlasticSourceControlCommand& InCommand) +{ + TRACE_CPUPROFILER_EVENT_SCOPE(FPlasticGetChangesetFilesWorker::Execute); + + check(InCommand.Operation->GetName() == GetName()); + TSharedRef Operation = StaticCastSharedRef(InCommand.Operation); + + if (!Operation->Changeset.IsValid()) + { + return false; + } + + { + InCommand.bCommandSuccessful = PlasticSourceControlUtils::RunGetChangesetFiles(Operation->Changeset.ToSharedRef(), Operation->Files, InCommand.ErrorMessages); + } + + { + InCommand.bCommandSuccessful &= PlasticSourceControlUtils::GetChangesetNumber(InCommand.ChangesetNumber, InCommand.ErrorMessages); + } + + return InCommand.bCommandSuccessful; +} + +bool FPlasticGetChangesetFilesWorker::UpdateStates() +{ + return false; +} + + FName FPlasticUpdateStatusWorker::GetName() const { return "UpdateStatus"; @@ -2566,6 +2613,7 @@ bool FPlasticUnshelveWorker::Execute(FPlasticSourceControlCommand& InCommand) TArray Parameters; Parameters.Add(TEXT("apply")); Parameters.Add(FString::Printf(TEXT("sh:%d"), ChangelistState->ShelveId)); + // TODO --noinput InCommand.bCommandSuccessful = PlasticSourceControlUtils::RunCommand(TEXT("shelveset"), Parameters, FilesToUnshelve, InCommand.InfoMessages, InCommand.ErrorMessages); } diff --git a/Source/PlasticSourceControl/Private/PlasticSourceControlOperations.h b/Source/PlasticSourceControl/Private/PlasticSourceControlOperations.h index eb82fefb..995005b1 100644 --- a/Source/PlasticSourceControl/Private/PlasticSourceControlOperations.h +++ b/Source/PlasticSourceControl/Private/PlasticSourceControlOperations.h @@ -21,6 +21,7 @@ class FPlasticSourceControlProvider; typedef TSharedRef FPlasticSourceControlBranchRef; typedef TSharedRef FPlasticSourceControlChangesetRef; +typedef TSharedPtr FPlasticSourceControlChangesetPtr; typedef TSharedRef FPlasticSourceControlLockRef; @@ -265,6 +266,25 @@ class FPlasticGetChangesets final : public FSourceControlOperationBase }; +/** + * Internal operation to list files in a changeset, using "cm log cs:" +*/ +class FPlasticGetChangesetFiles final : public FSourceControlOperationBase +{ +public: + // ISourceControlOperation interface + virtual FName GetName() const override; + + virtual FText GetInProgressString() const override; + + // Changeset to list files from + FPlasticSourceControlChangesetPtr Changeset; + + // List of files changed in the changeset + TArray Files; +}; + + /** Called when first activated on a project, and then at project load time. * Look for the root directory of the Plastic workspace (where the ".plastic/" subdirectory is located). */ class FPlasticConnectWorker final : public IPlasticSourceControlWorker @@ -611,6 +631,20 @@ class FPlasticGetChangesetsWorker final : public IPlasticSourceControlWorker int32 CurrentChangesetId; }; +/** list files in changeset. */ +class FPlasticGetChangesetFilesWorker final : public IPlasticSourceControlWorker +{ +public: + explicit FPlasticGetChangesetFilesWorker(FPlasticSourceControlProvider& InSourceControlProvider) + : IPlasticSourceControlWorker(InSourceControlProvider) + {} + virtual ~FPlasticGetChangesetFilesWorker() = default; + // IPlasticSourceControlWorker interface + virtual FName GetName() const override; + virtual bool Execute(class FPlasticSourceControlCommand& InCommand) override; + virtual bool UpdateStates() override; +}; + /** Plastic update the workspace to latest changes */ class FPlasticSyncWorker final : public IPlasticSourceControlWorker { diff --git a/Source/PlasticSourceControl/Private/PlasticSourceControlParsers.cpp b/Source/PlasticSourceControl/Private/PlasticSourceControlParsers.cpp index 115ee5fe..f515de02 100644 --- a/Source/PlasticSourceControl/Private/PlasticSourceControlParsers.cpp +++ b/Source/PlasticSourceControl/Private/PlasticSourceControlParsers.cpp @@ -750,8 +750,6 @@ static FString DecodeXmlEntities(const FString& InString) */ static bool ParseHistoryResults(const bool bInUpdateHistory, const FXmlFile& InXmlResult, TArray& InOutStates) { - TRACE_CPUPROFILER_EVENT_SCOPE(PlasticSourceControlParsers::ParseHistoryResults); - const FPlasticSourceControlProvider& Provider = FPlasticSourceControlModule::Get().GetProvider(); const FString RootRepSpec = FString::Printf(TEXT("%s@%s"), *Provider.GetRepositoryName(), *Provider.GetServerUrl()); @@ -948,11 +946,12 @@ bool ParseHistoryResults(const bool bInUpdateHistory, const FString& InXmlFilena FXmlFile XmlFile; { - TRACE_CPUPROFILER_EVENT_SCOPE(PlasticSourceControlParsers::ParseHistoryResults::FXmlFile::LoadFile); + TRACE_CPUPROFILER_EVENT_SCOPE(PlasticSourceControlParsers::ParseHistoryResults::LoadXml); bResult = XmlFile.LoadFile(InXmlFilename); } if (bResult) { + TRACE_CPUPROFILER_EVENT_SCOPE(PlasticSourceControlParsers::ParseHistoryResults::ParseXml); bResult = ParseHistoryResults(bInUpdateHistory, XmlFile, InOutStates); } else @@ -979,8 +978,6 @@ bool ParseHistoryResults(const bool bInUpdateHistory, const FString& InXmlFilena */ static bool ParseUpdateResults(const FXmlFile& InXmlResult, TArray& OutFiles) { - TRACE_CPUPROFILER_EVENT_SCOPE(PlasticSourceControlParsers::ParseUpdateResults); - static const FString UpdatedItems(TEXT("UpdatedItems")); static const FString List(TEXT("List")); static const FString UpdatedItem(TEXT("UpdatedItem")); @@ -1021,11 +1018,12 @@ bool ParseUpdateResults(const FString& InResults, TArray& OutFiles) FXmlFile XmlFile; { - TRACE_CPUPROFILER_EVENT_SCOPE(PlasticSourceControlParsers::ParseUpdateResults::FXmlFile::LoadFile); + TRACE_CPUPROFILER_EVENT_SCOPE(PlasticSourceControlParsers::ParseUpdateResults::LoadXml); bResult = XmlFile.LoadFile(InResults, EConstructMethod::ConstructFromBuffer); } if (bResult) { + TRACE_CPUPROFILER_EVENT_SCOPE(PlasticSourceControlParsers::ParseUpdateResults::ParseXml); bResult = ParseUpdateResults(XmlFile, OutFiles); } else @@ -1072,6 +1070,8 @@ bool ParseUpdateResults(const TArray& InResults, TArray& OutFi /// Parse checkin result, usually looking like "Created changeset cs:8@br:/main@MyProject@SRombauts@cloud (mount:'/')" FText ParseCheckInResults(const TArray& InResults) { + TRACE_CPUPROFILER_EVENT_SCOPE(PlasticSourceControlParsers::ParseCheckInResults); + if (InResults.Num() > 0) { static const FString ChangesetPrefix(TEXT("Created changeset ")); @@ -1133,8 +1133,6 @@ FText ParseCheckInResults(const TArray& InResults) */ static bool ParseChangelistsResults(const FXmlFile& InXmlResult, TArray& OutChangelistsStates, TArray>& OutCLFilesStates) { - TRACE_CPUPROFILER_EVENT_SCOPE(PlasticSourceControlParsers::ParseChangelistsResults); - static const FString StatusOutput(TEXT("StatusOutput")); static const FString WkConfigType(TEXT("WkConfigType")); static const FString WkConfigName(TEXT("WkConfigName")); @@ -1146,6 +1144,7 @@ static bool ParseChangelistsResults(const FXmlFile& InXmlResult, TArrayGetContent(), bUsesCheckedOutChanged); } - OutCLFilesStates[ChangelistIndex].Add(MoveTemp(FileState)); + + if (FileState.WorkspaceState == EWorkspaceState::Moved) + { + if (const FXmlNode* OldPathNode = ChangeNode->FindChildNode(OldPath)) + { + FileState.MovedFrom = FPaths::ConvertRelativePathToFull(WorkspaceRoot, OldPathNode->GetContent()); + } + } + + // Note: in case of a Moved file, it appears twice in the list; just update the first entry (set as a "Changed") with the "Move" status + if (FPlasticSourceControlState* ExistingState = OutCLFilesStates[ChangelistIndex].FindByPredicate( + [&FileState](const FPlasticSourceControlState& InState) + { + return InState.GetFilename().Equals(FileState.GetFilename()); + })) + { + ExistingState->WorkspaceState = FileState.WorkspaceState; + ExistingState->MovedFrom = FileState.MovedFrom; + } + else + { + OutCLFilesStates[ChangelistIndex].Add(MoveTemp(FileState)); + } } } @@ -1227,11 +1248,12 @@ bool ParseChangelistsResults(const FString& InXmlFilename, TArray&& InResults, TArray& OutBaseRevisions) { + TRACE_CPUPROFILER_EVENT_SCOPE(PlasticSourceControlParsers::ParseShelveDiffResults); + bool bResult = true; OutBaseRevisions.Reset(InResults.Num()); @@ -1441,7 +1466,7 @@ bool ParseShelveDiffResults(const FString InWorkspaceRoot, TArray&& InR if (ShelveState == EWorkspaceState::Moved) { - // In case of a Moved file, it appears twice in the list, so update the first entry (set as a "Changed" but has the Base Revision Id) and update it with the "Move" status + // Note: in case of a Moved file, it appears twice in the list; just update the first entry (set as a "Changed") with the "Move" status if (FPlasticSourceControlRevision* ExistingShelveRevision = OutBaseRevisions.FindByPredicate( [&AbsoluteFilename](const FPlasticSourceControlRevision& State) { @@ -1535,11 +1560,12 @@ bool ParseShelvesResult(const FString& InResults, FString& OutComment, FDateTime FXmlFile XmlFile; { - TRACE_CPUPROFILER_EVENT_SCOPE(PlasticSourceControlParsers::ParseShelvesResult); + TRACE_CPUPROFILER_EVENT_SCOPE(PlasticSourceControlParsers::ParseShelvesResult::LoadXml); bResult = XmlFile.LoadFile(InResults, EConstructMethod::ConstructFromBuffer); } if (bResult) { + TRACE_CPUPROFILER_EVENT_SCOPE(PlasticSourceControlParsers::ParseShelvesResult::ParseXml); int32 ShelveId; bResult = PlasticSourceControlParsers::ParseShelvesResult(XmlFile, ShelveId, OutComment, OutDate, OutOwner); } @@ -1638,11 +1664,12 @@ bool ParseChangesetsResults(const FString& InXmlFilename, TArray& OutFiles) +{ + static const FString Changes(TEXT("Changes")); + static const FString Item(TEXT("Item")); + static const FString Type(TEXT("Type")); + static const FString SrcCmPath(TEXT("SrcCmPath")); + static const FString DstCmPath(TEXT("DstCmPath")); + + if (const FXmlNode* ChangesNode = InChangesetNode->FindChildNode(Changes)) + { + const TArray& ItemNodes = ChangesNode->GetChildrenNodes(); + OutFiles.Reserve(ItemNodes.Num()); + for (const FXmlNode* ItemNode : ItemNodes) + { + check(ItemNode); + + const FXmlNode* PathNode = ItemNode->FindChildNode(DstCmPath); + const FXmlNode* TypeNode = ItemNode->FindChildNode(Type); + if ((PathNode == nullptr) || (TypeNode == nullptr)) + { + continue; + } + + // Here we make sure to only collect file states, not directories, since we shouldn't display the added directories to the Editor + FString FileName = PathNode->GetContent(); + int32 DotIndex; + if (FileName.FindChar(TEXT('.'), DotIndex)) + { + const EWorkspaceState WorkspaceState = StateFromType(TypeNode->GetContent()); + + FPlasticSourceControlStateRef State = MakeShareable(new FPlasticSourceControlState(MoveTemp(FileName), WorkspaceState)); + + if (WorkspaceState == EWorkspaceState::Moved) + { + if (const FXmlNode* SrcNode = ItemNode->FindChildNode(SrcCmPath)) + { + State->MovedFrom = SrcNode->GetContent(); + } + } + + // Add one revision to be able to fetch the file content for diff, if it's not marked for deletion. + if ((WorkspaceState != EWorkspaceState::Deleted) && State->History.IsEmpty()) + { + const TSharedRef SourceControlRevision = MakeShared(); + SourceControlRevision->State = &State.Get(); + SourceControlRevision->Filename = State->GetFilename(); + SourceControlRevision->ChangesetNumber = InChangeset->ChangesetId; // Note: for display in the diff window only + SourceControlRevision->Date = InChangeset->Date; // Note: not yet used for display as of UE5.2 + + State->History.Add(SourceControlRevision); + } + + // Note: in case of a Moved file, it appears twice in the list; just update the first entry (set as a "Changed") with the "Move" status + if (FPlasticSourceControlStateRef* ExistingState = OutFiles.FindByPredicate( + [&State](const TSharedRef& InState) + { + return InState->GetFilename().Equals(State->GetFilename()); + })) + { + (*ExistingState)->WorkspaceState = State->WorkspaceState; + (*ExistingState)->MovedFrom = State->MovedFrom; + } + else + { + OutFiles.Add(MoveTemp(State)); + } + } + } + } +} + +/** + * Parse results of the 'cm log cs: --xml --encoding="utf-8"' command. + * + * Results of the find command looks like the following: + + + + 2674 + 73 + /main/test + private files and folders + sebastien.rombauts@unity3d.com + cd803bd1-7d59-4573-b9de-1a4e684d573a + + + /main/test + 72 + sebastien.rombauts@unity3d.com + 2861 + -1 + /Private/Private.md + 2868 + /Private/Private.md + 2868 + 2024-04-03T14:59:31+02:00 + Added + + [...] + + 2024-04-02T16:20:11+02:00 + + +*/ +static bool ParseLogResults(const FXmlFile& InXmlResult, const FPlasticSourceControlChangesetRef& InChangeset, TArray& OutFiles) +{ + static const FString LogList(TEXT("LogList")); + static const FString ChangesetId(TEXT("ChangesetId")); + static const FString Branch(TEXT("Branch")); + static const FString Comment(TEXT("Comment")); + static const FString Owner(TEXT("Owner")); + static const FString Date(TEXT("Date")); + + const FXmlNode* LogListNode = InXmlResult.GetRootNode(); + if (LogListNode == nullptr || LogListNode->GetTag() != LogList) + { + return false; + } + + const TArray& ChangesetsNodes = LogListNode->GetChildrenNodes(); + if (ChangesetsNodes.Num() != 1 || ChangesetsNodes[0] == nullptr) + { + return false; + } + + FXmlNode* ChangesetNode = ChangesetsNodes[0]; + const FXmlNode* ChangesetIdNode = ChangesetNode->FindChildNode(ChangesetId); + if (ChangesetIdNode == nullptr || InChangeset->ChangesetId != FCString::Atoi(*ChangesetIdNode->GetContent())) + { + return false; + } + + // List Files States and create a Revision + ParseChangesInChangeset(ChangesetNode, InChangeset, OutFiles); + + return true; +} + +bool ParseLogResults(const FString& InXmlFilename, const FPlasticSourceControlChangesetRef& InChangeset, TArray& OutFiles) +{ + bool bResult = false; + + FXmlFile XmlFile; + { + TRACE_CPUPROFILER_EVENT_SCOPE(PlasticSourceControlParsers::ParseLogResults::LoadXml); + bResult = XmlFile.LoadFile(InXmlFilename); + } + if (bResult) + { + TRACE_CPUPROFILER_EVENT_SCOPE(PlasticSourceControlParsers::ParseLogResults::ParseXml); + bResult = ParseLogResults(XmlFile, InChangeset, OutFiles); + } + else + { + UE_LOG(LogSourceControl, Error, TEXT("ParseLogResults: XML parse error '%s'"), *XmlFile.GetLastError()) + } + + return bResult; +} + /** * Parse results of the 'cm find "branches where date >= 'YYYY-MM-DD' or changesets >= 'YYYY-MM-DD'" --xml --encoding="utf-8"' command. * @@ -1742,11 +1963,12 @@ bool ParseBranchesResults(const FString& InXmlFilename, TArray& OutFiles) { - TRACE_CPUPROFILER_EVENT_SCOPE(PlasticSourceControlUtils::ParseMergeResults); - static const FString Merge(TEXT("Merge")); static const FString Added(TEXT("Added")); static const FString Deleted(TEXT("Deleted")); @@ -1831,11 +2051,12 @@ bool ParseMergeResults(const FString& InResult, TArray& OutFiles) FXmlFile XmlFile; { - TRACE_CPUPROFILER_EVENT_SCOPE(PlasticSourceControlParsers::ParseMergeResults::FXmlFile::LoadFile); + TRACE_CPUPROFILER_EVENT_SCOPE(PlasticSourceControlParsers::ParseMergeResults::LoadXml); bResult = XmlFile.LoadFile(InResult, EConstructMethod::ConstructFromBuffer); } if (bResult) { + TRACE_CPUPROFILER_EVENT_SCOPE(PlasticSourceControlParsers::ParseMergeResults::ParseXml); bResult = ParseMergeResults(XmlFile, OutFiles); } else diff --git a/Source/PlasticSourceControl/Private/PlasticSourceControlParsers.h b/Source/PlasticSourceControl/Private/PlasticSourceControlParsers.h index c2dfebf1..9ab91826 100644 --- a/Source/PlasticSourceControl/Private/PlasticSourceControlParsers.h +++ b/Source/PlasticSourceControl/Private/PlasticSourceControlParsers.h @@ -12,6 +12,7 @@ class FPlasticSourceControlRevision; class FPlasticSourceControlState; typedef TSharedRef FPlasticSourceControlBranchRef; typedef TSharedRef FPlasticSourceControlChangesetRef; +typedef TSharedRef FPlasticSourceControlStateRef; namespace PlasticSourceControlParsers { @@ -84,6 +85,7 @@ bool ParseShelvesResult(const FString& InResults, FString& OutComment, FDateTime #endif bool ParseChangesetsResults(const FString& InXmlFilename, TArray& OutChangesets); +bool ParseLogResults(const FString& InXmlFilename, const FPlasticSourceControlChangesetRef& InChangeset, TArray& OutFiles); bool ParseBranchesResults(const FString& InXmlFilename, TArray& OutBranches); diff --git a/Source/PlasticSourceControl/Private/PlasticSourceControlState.cpp b/Source/PlasticSourceControl/Private/PlasticSourceControlState.cpp index a4d635f1..c9db0065 100644 --- a/Source/PlasticSourceControl/Private/PlasticSourceControlState.cpp +++ b/Source/PlasticSourceControl/Private/PlasticSourceControlState.cpp @@ -14,29 +14,62 @@ const TCHAR* FPlasticSourceControlState::ToString() const { - const TCHAR* WorkspaceStateStr = nullptr; switch (WorkspaceState) { - case EWorkspaceState::Unknown: WorkspaceStateStr = TEXT("Unknown"); break; - case EWorkspaceState::Ignored: WorkspaceStateStr = TEXT("Ignored"); break; - case EWorkspaceState::Controlled: WorkspaceStateStr = TEXT("Controlled"); break; - case EWorkspaceState::CheckedOutChanged: WorkspaceStateStr = TEXT("CheckedOutChanged"); break; - case EWorkspaceState::CheckedOutUnchanged: WorkspaceStateStr = TEXT("CheckedOutUnchanged"); break; - case EWorkspaceState::Added: WorkspaceStateStr = TEXT("Added"); break; - case EWorkspaceState::Moved: WorkspaceStateStr = TEXT("Moved"); break; - case EWorkspaceState::Copied: WorkspaceStateStr = TEXT("Copied"); break; - case EWorkspaceState::Replaced: WorkspaceStateStr = TEXT("Replaced"); break; - case EWorkspaceState::Deleted: WorkspaceStateStr = TEXT("Deleted"); break; - case EWorkspaceState::LocallyDeleted: WorkspaceStateStr = TEXT("LocallyDeleted"); break; - case EWorkspaceState::Changed: WorkspaceStateStr = TEXT("Changed"); break; - case EWorkspaceState::Conflicted: WorkspaceStateStr = TEXT("Conflicted"); break; - case EWorkspaceState::Private: WorkspaceStateStr = TEXT("Private"); break; - default: WorkspaceStateStr = TEXT("???"); break; - } - return WorkspaceStateStr; + case EWorkspaceState::Ignored: return TEXT("Ignored"); + case EWorkspaceState::Controlled: return TEXT("Controlled"); + case EWorkspaceState::CheckedOutChanged: return TEXT("Checked-out (changed)"); + case EWorkspaceState::CheckedOutUnchanged: return TEXT("Checked-out (unchanged)"); + case EWorkspaceState::Added: return TEXT("Added"); + case EWorkspaceState::Moved: return TEXT("Moved"); + case EWorkspaceState::Copied: return TEXT("Copied"); + case EWorkspaceState::Replaced: return TEXT("Replaced"); + case EWorkspaceState::Deleted: return TEXT("Deleted"); + case EWorkspaceState::LocallyDeleted: return TEXT("LocallyDeleted"); + case EWorkspaceState::Changed: return TEXT("Changed"); + case EWorkspaceState::Conflicted: return TEXT("Conflicted"); + case EWorkspaceState::Private: return TEXT("Private"); + case EWorkspaceState::Unknown: + default: return TEXT("Unknown"); + } +} + +FText FPlasticSourceControlState::ToText() const +{ + static const FText Ignored = LOCTEXT("Ignored", "Ignored"); + static const FText Controlled = LOCTEXT("Controlled", "Controlled"); + static const FText CheckedOutChanged = LOCTEXT("CheckedOutChanged", "Checked-out (changed)"); + static const FText CheckedOutUnchanged = LOCTEXT("CheckedOutUnchanged", "Checked-out (unchanged)"); + static const FText Added = LOCTEXT("Added", "Added"); + static const FText Moved = LOCTEXT("Moved", "Moved"); + static const FText Copied = LOCTEXT("Copied", "Copied"); + static const FText Replaced = LOCTEXT("Replaced", "Replaced"); + static const FText Deleted = LOCTEXT("Deleted", "Deleted"); + static const FText LocallyDeleted = LOCTEXT("LocallyDeleted", "LocallyDeleted"); + static const FText Changed = LOCTEXT("Changed", "Changed"); + static const FText Conflicted = LOCTEXT("Conflicted", "Conflicted"); + static const FText Private = LOCTEXT("Private", "Private"); + static const FText Unknown = LOCTEXT("Unknown", "Unknown"); + switch (WorkspaceState) + { + case EWorkspaceState::Ignored: return Ignored; + case EWorkspaceState::Controlled: return Controlled; + case EWorkspaceState::CheckedOutChanged: return CheckedOutChanged; + case EWorkspaceState::CheckedOutUnchanged: return CheckedOutUnchanged; + case EWorkspaceState::Added: return Added; + case EWorkspaceState::Moved: return Moved; + case EWorkspaceState::Copied: return Copied; + case EWorkspaceState::Replaced: return Replaced; + case EWorkspaceState::Deleted: return Deleted; + case EWorkspaceState::LocallyDeleted: return LocallyDeleted; + case EWorkspaceState::Changed: return Changed; + case EWorkspaceState::Conflicted: return Conflicted; + case EWorkspaceState::Private: return Private; + case EWorkspaceState::Unknown: + default: return Unknown; + } } - int32 FPlasticSourceControlState::GetHistorySize() const { return History.Num(); diff --git a/Source/PlasticSourceControl/Private/PlasticSourceControlState.h b/Source/PlasticSourceControl/Private/PlasticSourceControlState.h index 4ed5f468..a72db99e 100644 --- a/Source/PlasticSourceControl/Private/PlasticSourceControlState.h +++ b/Source/PlasticSourceControl/Private/PlasticSourceControlState.h @@ -119,6 +119,13 @@ class FPlasticSourceControlState : public ISourceControlState // debug log utility const TCHAR* ToString() const; + FText ToText() const; + + void PopulateSearchString(TArray& OutStrings) const + { + OutStrings.Emplace(LocalFilename); + } + /** ISourceControlState interface */ virtual int32 GetHistorySize() const override; virtual TSharedPtr GetHistoryItem(int32 HistoryIndex) const override; @@ -251,3 +258,6 @@ class FPlasticSourceControlState : public ISourceControlState /** The change list of the last modification */ int32 HeadChangeList; }; + +typedef TSharedRef FPlasticSourceControlStateRef; +typedef TSharedPtr FPlasticSourceControlStatePtr; diff --git a/Source/PlasticSourceControl/Private/PlasticSourceControlUtils.cpp b/Source/PlasticSourceControl/Private/PlasticSourceControlUtils.cpp index 7b494be6..fade0f0a 100644 --- a/Source/PlasticSourceControl/Private/PlasticSourceControlUtils.cpp +++ b/Source/PlasticSourceControl/Private/PlasticSourceControlUtils.cpp @@ -1249,6 +1249,30 @@ bool RunGetChangesets(const FDateTime& InFromDate, TArray& OutFiles, TArray& OutErrorMessages) +{ + bool bCommandSuccessful = false; + + const FScopedTempFile LogResultFile; + FString Results; + FString Errors; + TArray Parameters; + Parameters.Add(FString::Printf(TEXT("cs:%d"), InChangeset->ChangesetId)); + Parameters.Add(FString::Printf(TEXT("--xml=\"%s\""), *LogResultFile.GetFilename())); + Parameters.Add(TEXT("--encoding=\"utf-8\"")); + bCommandSuccessful = PlasticSourceControlUtils::RunCommand(TEXT("log"), Parameters, TArray(), Results, Errors); + if (bCommandSuccessful && FPaths::FileExists(LogResultFile.GetFilename())) + { + bCommandSuccessful = PlasticSourceControlParsers::ParseLogResults(LogResultFile.GetFilename(), InChangeset, OutFiles); + } + if (!Errors.IsEmpty()) + { + OutErrorMessages.Add(MoveTemp(Errors)); + } + + return bCommandSuccessful; +} + bool RunGetBranches(const FDateTime& InFromDate, TArray& OutBranches, TArray& OutErrorMessages) { bool bCommandSuccessful; diff --git a/Source/PlasticSourceControl/Private/PlasticSourceControlUtils.h b/Source/PlasticSourceControl/Private/PlasticSourceControlUtils.h index 5ced0f60..eae7090c 100644 --- a/Source/PlasticSourceControl/Private/PlasticSourceControlUtils.h +++ b/Source/PlasticSourceControl/Private/PlasticSourceControlUtils.h @@ -16,6 +16,7 @@ struct FSoftwareVersion; typedef TSharedRef FPlasticSourceControlBranchRef; typedef TSharedRef FPlasticSourceControlChangesetRef; typedef TSharedRef FPlasticSourceControlLockRef; +typedef TSharedRef FPlasticSourceControlStateRef; enum class EWorkspaceState; @@ -304,13 +305,23 @@ void AddShelvedFileToChangelist(FPlasticSourceControlChangelistState& InOutChang #endif /** - * Run find "changesets where date >= 'YYYY-MM-DD'" and "log --xml" and parse the results. + * Run find "changesets where date >= 'YYYY-MM-DD'" and parse the results. * @param InFromDate The date to search from - * @param OutChangesets The list of changesets, with files and their states + * @param OutChangesets The list of changesets, without their files * @param OutErrorMessages Any errors (from StdErr) as an array per-line + * + * @see RunGetChangesetFiles() below used to populated a specific changeset with its list of files */ bool RunGetChangesets(const FDateTime& InFromDate, TArray& OutChangesets, TArray& OutErrorMessages); +/** + * Run "log cs: --xml" and parse the results to populate the files from the specified changeset. + * @param InChangeset The changeset to get the files changed + * @param OutFiles The files changed in the specified changeset + * @param OutErrorMessages Any errors (from StdErr) as an array per-line + */ +bool RunGetChangesetFiles(const FPlasticSourceControlChangesetRef& InChangeset, TArray& OutFiles, TArray& OutErrorMessages); + /** * Run find "branches where date >= 'YYYY-MM-DD' or changesets >= 'YYYY-MM-DD'" and parse the results. * @param InFromDate The date to search from diff --git a/Source/PlasticSourceControl/Private/SPlasticSourceControlBranchRow.cpp b/Source/PlasticSourceControl/Private/SPlasticSourceControlBranchRow.cpp index 07c05de8..7ecf0be3 100644 --- a/Source/PlasticSourceControl/Private/SPlasticSourceControlBranchRow.cpp +++ b/Source/PlasticSourceControl/Private/SPlasticSourceControlBranchRow.cpp @@ -95,8 +95,12 @@ TSharedRef SPlasticSourceControlBranchRow::GenerateWidgetForColumn(cons } else if (InColumnId == PlasticSourceControlBranchesListViewColumn::Comment::Id()) { + // Make each comment fit on a single line to preserve the table layout + FString CommentOnOneLine = BranchToVisualize->Comment; + CommentOnOneLine.ReplaceCharInline(TEXT('\n'), TEXT(' '), ESearchCase::CaseSensitive); + return SNew(STextBlock) - .Text(FText::FromString(BranchToVisualize->Comment)) + .Text(FText::FromString(MoveTemp(CommentOnOneLine))) .ToolTipText(FText::FromString(BranchToVisualize->Comment)) .Margin(FMargin(6.f, 1.f)) #if ENGINE_MAJOR_VERSION >= 5 diff --git a/Source/PlasticSourceControl/Private/SPlasticSourceControlBranchesWidget.cpp b/Source/PlasticSourceControl/Private/SPlasticSourceControlBranchesWidget.cpp index 71824b17..296265b7 100644 --- a/Source/PlasticSourceControl/Private/SPlasticSourceControlBranchesWidget.cpp +++ b/Source/PlasticSourceControl/Private/SPlasticSourceControlBranchesWidget.cpp @@ -6,6 +6,7 @@ #include "PlasticSourceControlOperations.h" #include "PlasticSourceControlProjectSettings.h" #include "PlasticSourceControlBranch.h" +#include "PlasticSourceControlUtils.h" #include "PlasticSourceControlVersions.h" #include "SPlasticSourceControlBranchRow.h" #include "SPlasticSourceControlCreateBranch.h" @@ -32,6 +33,7 @@ #endif #include "Framework/Application/SlateApplication.h" #include "Framework/Docking/TabManager.h" +#include "Widgets/Images/SImage.h" #include "Widgets/Input/SComboButton.h" #include "Widgets/Input/SSearchBox.h" #include "Widgets/Layout/SSpacer.h" @@ -77,45 +79,162 @@ void SPlasticSourceControlBranchesWidget::Construct(const FArguments& InArgs) [ SNew(SHorizontalBox) +SHorizontalBox::Slot() - .HAlign(HAlign_Left) - .VAlign(VAlign_Center) - .AutoWidth() - [ - CreateToolBar() - ] - +SHorizontalBox::Slot() - .MaxWidth(10.0f) + .FillWidth(1.0f) [ - SNew(SSpacer) + SNew(SHorizontalBox) + +SHorizontalBox::Slot() + .HAlign(HAlign_Left) + .VAlign(VAlign_Center) + .AutoWidth() + [ + CreateToolBar() + ] + +SHorizontalBox::Slot() + .MaxWidth(10.0f) + [ + SNew(SSpacer) + ] + +SHorizontalBox::Slot() + .VAlign(VAlign_Center) + .MaxWidth(300.0f) + [ + SAssignNew(BranchSearchBox, SSearchBox) + .HintText(LOCTEXT("SearchBranches", "Search Branches")) + .ToolTipText(LOCTEXT("PlasticBranchesSearch_Tooltip", "Filter the list of branches by keyword.")) + .OnTextChanged(this, &SPlasticSourceControlBranchesWidget::OnSearchTextChanged) + ] + +SHorizontalBox::Slot() + .VAlign(VAlign_Center) + .MaxWidth(125.0f) + .Padding(10.f, 0.f) + [ + SNew(SComboButton) + .ToolTipText(LOCTEXT("PlasticBranchesDate_Tooltip", "Filter the list of branches by date of activity.")) + .OnGetMenuContent(this, &SPlasticSourceControlBranchesWidget::BuildFromDateDropDownMenu) + .ButtonContent() + [ + SNew(STextBlock) + .Text_Lambda([this]() { return FromDateInDaysValues[FromDateInDays]; }) + ] + ] ] +SHorizontalBox::Slot() + .HAlign(HAlign_Right) .VAlign(VAlign_Center) - .MaxWidth(300.0f) + .AutoWidth() [ - SAssignNew(FileSearchBox, SSearchBox) - .HintText(LOCTEXT("SearchBranches", "Search Branches")) - .ToolTipText(LOCTEXT("PlasticBranchesSearch_Tooltip", "Filter the list of branches by keyword.")) - .OnTextChanged(this, &SPlasticSourceControlBranchesWidget::OnSearchTextChanged) + // Button to open the Changesets View + SNew(SButton) + .ContentPadding(FMargin(6.0f, 0.0f)) + .ToolTipText(LOCTEXT("PlasticChangesetsWindowTooltip", "Open the Changesets window.")) +#if ENGINE_MAJOR_VERSION == 5 && ENGINE_MINOR_VERSION >= 1 + .ButtonStyle(FAppStyle::Get(), "SimpleButton") +#else + .ButtonStyle(FEditorStyle::Get(), "SimpleButton") +#endif + .OnClicked_Lambda([]() + { + FPlasticSourceControlModule::Get().GetChangesetsWindow().OpenTab(); + return FReply::Handled(); + }) + [ + SNew(SHorizontalBox) + +SHorizontalBox::Slot() + .AutoWidth() + .VAlign(VAlign_Center) + .HAlign(HAlign_Center) + [ + SNew(SImage) +#if ENGINE_MAJOR_VERSION == 5 && ENGINE_MINOR_VERSION >= 1 + .Image(FAppStyle::GetBrush("SourceControl.Actions.History")) +#else + .Image(FEditorStyle::GetBrush("SourceControl.Actions.History")) +#endif + ] + +SHorizontalBox::Slot() + .AutoWidth() + .VAlign(VAlign_Center) + .Padding(5.0f, 0.0f, 0.0f, 0.0f) + [ + SNew(STextBlock) +#if ENGINE_MAJOR_VERSION == 5 && ENGINE_MINOR_VERSION >= 1 + .TextStyle(&FAppStyle::Get().GetWidgetStyle("NormalText")) +#else + .TextStyle(&FEditorStyle::Get().GetWidgetStyle("NormalText")) +#endif + .Text(LOCTEXT("PlasticChangesetsWindow", "Changesets")) + ] + ] ] +SHorizontalBox::Slot() + .HAlign(HAlign_Right) .VAlign(VAlign_Center) - .MaxWidth(125.0f) - .Padding(10.f, 0.f) + .AutoWidth() [ - SNew(SComboButton) - .ToolTipText(LOCTEXT("PlasticBranchesDate_Tooltip", "Filter the list of branches by date of creation.")) - .OnGetMenuContent(this, &SPlasticSourceControlBranchesWidget::BuildFromDateDropDownMenu) - .ButtonContent() + // Button to open the Branch Explorer + SNew(SButton) + .ContentPadding(FMargin(6.0f, 0.0f)) + .ToolTipText(LOCTEXT("PlasticBranchExplorerTooltip", "Open the Branch Explorer of the Desktop Application for the current workspace.")) +#if ENGINE_MAJOR_VERSION == 5 && ENGINE_MINOR_VERSION >= 1 + .ButtonStyle(FAppStyle::Get(), "SimpleButton") +#else + .ButtonStyle(FEditorStyle::Get(), "SimpleButton") +#endif + .OnClicked_Lambda([]() + { + PlasticSourceControlUtils::OpenDesktopApplication(true); + return FReply::Handled(); + }) [ - SNew(STextBlock) - .Text_Lambda([this]() { return FromDateInDaysValues[FromDateInDays]; }) + SNew(SHorizontalBox) + +SHorizontalBox::Slot() + .AutoWidth() + .VAlign(VAlign_Center) + .HAlign(HAlign_Center) + [ + SNew(SImage) +#if ENGINE_MAJOR_VERSION == 5 && ENGINE_MINOR_VERSION >= 1 + .Image(FAppStyle::GetBrush("SourceControl.Branch")) +#else + .Image(FEditorStyle::GetBrush("SourceControl.Branch")) +#endif + ] + +SHorizontalBox::Slot() + .AutoWidth() + .VAlign(VAlign_Center) + .Padding(5.0f, 0.0f, 0.0f, 0.0f) + [ + SNew(STextBlock) +#if ENGINE_MAJOR_VERSION == 5 && ENGINE_MINOR_VERSION >= 1 + .TextStyle(&FAppStyle::Get().GetWidgetStyle("NormalText")) +#else + .TextStyle(&FEditorStyle::Get().GetWidgetStyle("NormalText")) +#endif + .Text(LOCTEXT("OpenBranchExplorer", "Branch Explorer")) + ] ] ] ] ] +SVerticalBox::Slot() // The main content: the list of branches [ - CreateContentPanel() + SNew(SVerticalBox) + +SVerticalBox::Slot() + .Padding(5.0f) + .AutoHeight() + [ + CreateContentPanel() + ] + +SVerticalBox::Slot() + .VAlign(VAlign_Center) + .HAlign(HAlign_Center) + .FillHeight(1.0f) + [ + // Text to display when there is no branch displayed + SNew(STextBlock) + .Text(LOCTEXT("NoBranch", "There is no branch to display.")) + .Visibility_Lambda([this]() { return SourceControlBranches.Num() ? EVisibility::Collapsed : EVisibility::Visible; }) + ] ] +SVerticalBox::Slot() // Status bar (Always visible) .AutoHeight() @@ -153,8 +272,7 @@ TSharedRef SPlasticSourceControlBranchesWidget::CreateToolBar() #endif ToolBarBuilder.AddToolBarButton( - FUIAction( - FExecuteAction::CreateLambda([this]() { RequestBranchesRefresh(); })), + FUIAction(FExecuteAction::CreateLambda([this]() { bShouldRefresh = true; })), NAME_None, LOCTEXT("SourceControl_RefreshButton", "Refresh"), LOCTEXT("SourceControl_RefreshButton_Tooltip", "Refreshes branches from revision control provider."), @@ -258,7 +376,7 @@ TSharedRef SPlasticSourceControlBranchesWidget::OnGenerateRow(FPlasti return SNew(SPlasticSourceControlBranchRow, OwnerTable) .BranchToVisualize(InBranch) .bIsCurrentBranch(bIsCurrentBranch) - .HighlightText_Lambda([this]() { return FileSearchBox->GetText(); }); + .HighlightText_Lambda([this]() { return BranchSearchBox->GetText(); }); } void SPlasticSourceControlBranchesWidget::OnHiddenColumnsListChanged() @@ -298,7 +416,7 @@ void SPlasticSourceControlBranchesWidget::OnHiddenColumnsListChanged() void SPlasticSourceControlBranchesWidget::OnSearchTextChanged(const FText& InFilterText) { SearchTextFilter->SetRawFilterText(InFilterText); - FileSearchBox->SetError(SearchTextFilter->GetFilterErrorText()); + BranchSearchBox->SetError(SearchTextFilter->GetFilterErrorText()); } void SPlasticSourceControlBranchesWidget::PopulateItemSearchStrings(const FPlasticSourceControlBranch& InItem, TArray& OutStrings) @@ -309,8 +427,7 @@ void SPlasticSourceControlBranchesWidget::PopulateItemSearchStrings(const FPlast void SPlasticSourceControlBranchesWidget::OnFromDateChanged(int32 InFromDateInDays) { FromDateInDays = InFromDateInDays; - - RequestBranchesRefresh(); + bShouldRefresh = true; } TSharedRef SPlasticSourceControlBranchesWidget::BuildFromDateDropDownMenu() @@ -1127,7 +1244,7 @@ FReply SPlasticSourceControlBranchesWidget::OnKeyDown(const FGeometry& MyGeometr if (InKeyEvent.GetKey() == EKeys::F5) { // Pressing F5 refreshes the list of branches - RequestBranchesRefresh(); + bShouldRefresh = true; return FReply::Handled(); } else if (InKeyEvent.GetKey() == EKeys::Enter) diff --git a/Source/PlasticSourceControl/Private/SPlasticSourceControlBranchesWidget.h b/Source/PlasticSourceControl/Private/SPlasticSourceControlBranchesWidget.h index 1515b0ed..e1f4f3a2 100644 --- a/Source/PlasticSourceControl/Private/SPlasticSourceControlBranchesWidget.h +++ b/Source/PlasticSourceControl/Private/SPlasticSourceControlBranchesWidget.h @@ -102,7 +102,7 @@ class SPlasticSourceControlBranchesWidget : public SCompoundWidget virtual FReply OnKeyDown(const FGeometry& MyGeometry, const FKeyEvent& InKeyEvent) override; private: - TSharedPtr FileSearchBox; + TSharedPtr BranchSearchBox; FName PrimarySortedColumn; FName SecondarySortedColumn; diff --git a/Source/PlasticSourceControl/Private/SPlasticSourceControlChangesetFileRow.cpp b/Source/PlasticSourceControl/Private/SPlasticSourceControlChangesetFileRow.cpp new file mode 100644 index 00000000..6d99641a --- /dev/null +++ b/Source/PlasticSourceControl/Private/SPlasticSourceControlChangesetFileRow.cpp @@ -0,0 +1,106 @@ +// Copyright (c) 2024 Unity Technologies + +#include "SPlasticSourceControlChangesetFileRow.h" + +#include "PlasticSourceControlState.h" +#include "PlasticSourceControlUtils.h" + +#include "Widgets/Images/SImage.h" +#include "Widgets/Images/SLayeredImage.h" +#include "Widgets/Text/STextBlock.h" + +#include "Runtime/Launch/Resources/Version.h" +#if ENGINE_MAJOR_VERSION == 5 && ENGINE_MINOR_VERSION >= 1 +#include "Styling/AppStyle.h" +#else +#include "EditorStyleSet.h" +#endif + +#define LOCTEXT_NAMESPACE "PlasticSourceControlChangesetFileWindow" + +FName PlasticSourceControlChangesetFilesListViewColumn::Icon::Id() { return TEXT("Icon"); } +FText PlasticSourceControlChangesetFilesListViewColumn::Icon::GetDisplayText() { return LOCTEXT("Icon_Column", "Revision Control Status"); } +FText PlasticSourceControlChangesetFilesListViewColumn::Icon::GetToolTipText() { return LOCTEXT("Icon_Column_Tooltip", "Icon displaying the type of change"); } + +FName PlasticSourceControlChangesetFilesListViewColumn::Name::Id() { return TEXT("Name"); } +FText PlasticSourceControlChangesetFilesListViewColumn::Name::GetDisplayText() { return LOCTEXT("Name_Column", "Name"); } +FText PlasticSourceControlChangesetFilesListViewColumn::Name::GetToolTipText() { return LOCTEXT("Name_Column_Tooltip", "Name of the file"); } + +FName PlasticSourceControlChangesetFilesListViewColumn::Path::Id() { return TEXT("Path"); } +FText PlasticSourceControlChangesetFilesListViewColumn::Path::GetDisplayText() { return LOCTEXT("Path_Column", "Path"); } +FText PlasticSourceControlChangesetFilesListViewColumn::Path::GetToolTipText() { return LOCTEXT("Path_Column_Tooltip", "Path of the file relative to the workspace"); } + +void SPlasticSourceControlChangesetFileRow::Construct(const FArguments& InArgs, const TSharedRef& InOwner) +{ + FileToVisualize = InArgs._FileToVisualize.Get(); + HighlightText = InArgs._HighlightText; + + FSuperRowType::FArguments Args = FSuperRowType::FArguments() + .ShowSelection(true); + FSuperRowType::Construct(Args, InOwner); +} + +TSharedRef GetSCCFileWidget(FPlasticSourceControlState* InFileState) +{ + 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) + ] + // Source control state + +SOverlay::Slot() + .HAlign(HAlign_Left) + .VAlign(VAlign_Top) + [ + SNew(SBox) + .WidthOverride(IconOverlaySize) + .HeightOverride(IconOverlaySize) + [ + SNew(SLayeredImage, InFileState->GetIcon()) + .ToolTipText(InFileState->GetDisplayTooltip()) + ] + ]; +} + + +TSharedRef SPlasticSourceControlChangesetFileRow::GenerateWidgetForColumn(const FName& InColumnId) +{ + if (InColumnId == PlasticSourceControlChangesetFilesListViewColumn::Icon::Id()) + { + return SNew(SBox) + .WidthOverride(16) // Small Icons are usually 16x16 + .ToolTipText(FileToVisualize->ToText()) + .HAlign(HAlign_Center) + [ + GetSCCFileWidget(FileToVisualize) + ]; + } + else if (InColumnId == PlasticSourceControlChangesetFilesListViewColumn::Name::Id()) + { + return SNew(STextBlock) + .Text(FText::FromString(FPaths::GetBaseFilename(FileToVisualize->LocalFilename, true))) // Just the name without its path or extension + .ToolTipText(FText::FromString(FPaths::GetCleanFilename(FileToVisualize->LocalFilename))) // Name with extension + .HighlightText(HighlightText); + } + else if (InColumnId == PlasticSourceControlChangesetFilesListViewColumn::Path::Id()) + { + return SNew(STextBlock) + .Text(FText::FromString(FPaths::GetBaseFilename(FileToVisualize->LocalFilename, false))) // Relative Path without the extension + .ToolTipText(FText::FromString(FileToVisualize->LocalFilename)) + .HighlightText(HighlightText); + } + else + { + return SNullWidget::NullWidget; + } +} + +#undef LOCTEXT_NAMESPACE diff --git a/Source/PlasticSourceControl/Private/SPlasticSourceControlChangesetFileRow.h b/Source/PlasticSourceControl/Private/SPlasticSourceControlChangesetFileRow.h new file mode 100644 index 00000000..d2f8f05e --- /dev/null +++ b/Source/PlasticSourceControl/Private/SPlasticSourceControlChangesetFileRow.h @@ -0,0 +1,76 @@ +// Copyright (c) 2024 Unity Technologies + +#pragma once + +#include "CoreMinimal.h" + +#include "Widgets/DeclarativeSyntaxSupport.h" +#include "Widgets/Views/SListView.h" +#include "Widgets/Views/STableRow.h" +#include "Widgets/Views/STableViewBase.h" + +typedef TSharedRef FPlasticSourceControlStateRef; +typedef TSharedPtr FPlasticSourceControlStatePtr; + +class FPlasticSourceControlState; + +/** Lists the unique columns used in the list view displaying Files in the selected changeset. */ +namespace PlasticSourceControlChangesetFilesListViewColumn +{ + /** The Icon displaying the type of Change column. */ + namespace Icon // NOLINT(runtime/indentation_namespace) + { + FName Id(); + FText GetDisplayText(); + FText GetToolTipText(); + }; + + /** The File Name column. */ + namespace Name // NOLINT(runtime/indentation_namespace) + { + FName Id(); + FText GetDisplayText(); + FText GetToolTipText(); + }; + + /** The File Path column. */ + namespace Path // NOLINT(runtime/indentation_namespace) + { + FName Id(); + FText GetDisplayText(); + FText GetToolTipText(); + }; + +} // namespace PlasticSourceControlChangesetFilesListViewColumn + +class SPlasticSourceControlChangesetFileRow : public SMultiColumnTableRow +{ +public: + SLATE_BEGIN_ARGS(SPlasticSourceControlChangesetFileRow) + : _FileToVisualize(nullptr) + , _HighlightText() + { + } + SLATE_ARGUMENT(FPlasticSourceControlStatePtr, FileToVisualize) + SLATE_ATTRIBUTE(FText, HighlightText) + SLATE_END_ARGS() + +public: + /** + * Construct a row child widgets of the ListView. + * + * @param InArgs Parameters including the File to visualize in this row. + * @param InOwner The owning ListView. + */ + void Construct(const FArguments& InArgs, const TSharedRef& InOwner); + + // SMultiColumnTableRow overrides + virtual TSharedRef GenerateWidgetForColumn(const FName& ColumnId) override; + +private: + /** The File that we are visualizing in this row. */ + FPlasticSourceControlState* FileToVisualize; + + /** The search text to highlight if any */ + TAttribute HighlightText; +}; diff --git a/Source/PlasticSourceControl/Private/SPlasticSourceControlChangesetRow.cpp b/Source/PlasticSourceControl/Private/SPlasticSourceControlChangesetRow.cpp index e03da7d0..d14cc992 100644 --- a/Source/PlasticSourceControl/Private/SPlasticSourceControlChangesetRow.cpp +++ b/Source/PlasticSourceControl/Private/SPlasticSourceControlChangesetRow.cpp @@ -83,8 +83,12 @@ TSharedRef SPlasticSourceControlChangesetRow::GenerateWidgetForColumn(c } else if (InColumnId == PlasticSourceControlChangesetsListViewColumn::Comment::Id()) { + // Make each comment fit on a single line to preserve the table layout + FString CommentOnOneLine = ChangesetToVisualize->Comment; + CommentOnOneLine.ReplaceCharInline(TEXT('\n'), TEXT(' '), ESearchCase::CaseSensitive); + return SNew(STextBlock) - .Text(FText::FromString(ChangesetToVisualize->Comment)) + .Text(FText::FromString(MoveTemp(CommentOnOneLine))) .ToolTipText(FText::FromString(ChangesetToVisualize->Comment)) .Margin(FMargin(6.f, 1.f)) #if ENGINE_MAJOR_VERSION >= 5 diff --git a/Source/PlasticSourceControl/Private/SPlasticSourceControlChangesetsWidget.cpp b/Source/PlasticSourceControl/Private/SPlasticSourceControlChangesetsWidget.cpp index 9d31ff7f..060c2cc2 100644 --- a/Source/PlasticSourceControl/Private/SPlasticSourceControlChangesetsWidget.cpp +++ b/Source/PlasticSourceControl/Private/SPlasticSourceControlChangesetsWidget.cpp @@ -8,6 +8,7 @@ #include "PlasticSourceControlProjectSettings.h" #include "PlasticSourceControlUtils.h" #include "SPlasticSourceControlChangesetRow.h" +#include "SPlasticSourceControlChangesetFileRow.h" #include "PackageUtils.h" @@ -27,6 +28,7 @@ #else #include "EditorStyleSet.h" #endif +#include "Widgets/Images/SImage.h" #include "Widgets/Input/SComboButton.h" #include "Widgets/Input/SSearchBox.h" #include "Widgets/Layout/SSpacer.h" @@ -44,8 +46,11 @@ void SPlasticSourceControlChangesetsWidget::Construct(const FArguments& InArgs) CurrentChangesetId = FPlasticSourceControlModule::Get().GetProvider().GetChangesetNumber(); - SearchTextFilter = MakeShared>(TTextFilter::FItemToStringArray::CreateSP(this, &SPlasticSourceControlChangesetsWidget::PopulateItemSearchStrings)); - SearchTextFilter->OnChanged().AddSP(this, &SPlasticSourceControlChangesetsWidget::OnRefreshUI); + ChangesetsSearchTextFilter = MakeShared>(TTextFilter::FItemToStringArray::CreateSP(this, &SPlasticSourceControlChangesetsWidget::PopulateItemSearchStrings)); + ChangesetsSearchTextFilter->OnChanged().AddSP(this, &SPlasticSourceControlChangesetsWidget::OnChangesetsRefreshUI); + + FilesSearchTextFilter = MakeShared>(TTextFilter::FItemToStringArray::CreateSP(this, &SPlasticSourceControlChangesetsWidget::PopulateItemSearchStrings)); + FilesSearchTextFilter->OnChanged().AddSP(this, &SPlasticSourceControlChangesetsWidget::OnFilesRefreshUI); FromDateInDaysValues.Add(TPair(7, FText::FromString(TEXT("Last week")))); FromDateInDaysValues.Add(TPair(15, FText::FromString(TEXT("Last 15 days")))); @@ -55,6 +60,10 @@ void SPlasticSourceControlChangesetsWidget::Construct(const FArguments& InArgs) FromDateInDaysValues.Add(TPair(365, FText::FromString(TEXT("Last year")))); FromDateInDaysValues.Add(TPair(-1, FText::FromString(TEXT("All time")))); + // Min/Max prevents making the Changeset Area too small + const float ChangesetAreaRatio = 0.6f; + const float FileAreaRatio = 1.0f - ChangesetAreaRatio; + ChildSlot [ SNew(SVerticalBox) @@ -71,46 +80,160 @@ void SPlasticSourceControlChangesetsWidget::Construct(const FArguments& InArgs) [ SNew(SHorizontalBox) +SHorizontalBox::Slot() - .HAlign(HAlign_Left) + .FillWidth(1.0f) + [ + SNew(SHorizontalBox) + +SHorizontalBox::Slot() + .HAlign(HAlign_Left) + .VAlign(VAlign_Center) + .AutoWidth() + [ + CreateToolBar() + ] + +SHorizontalBox::Slot() + .MaxWidth(10.0f) + [ + SNew(SSpacer) + ] + +SHorizontalBox::Slot() + .VAlign(VAlign_Center) + .MaxWidth(300.0f) + [ + SAssignNew(ChangesetsSearchBox, SSearchBox) + .HintText(LOCTEXT("SearchChangesets", "Search changesets")) + .ToolTipText(LOCTEXT("PlasticChangesetsSearch_Tooltip", "Filter the list of changesets by keyword.")) + .OnTextChanged(this, &SPlasticSourceControlChangesetsWidget::OnChangesetsSearchTextChanged) + ] + +SHorizontalBox::Slot() + .VAlign(VAlign_Center) + .MaxWidth(125.0f) + .Padding(10.f, 0.f) + [ + SNew(SComboButton) + .ToolTipText(LOCTEXT("PlasticChangesetesDate_Tooltip", "Filter the list of changesets by date of creation.")) + .OnGetMenuContent(this, &SPlasticSourceControlChangesetsWidget::BuildFromDateDropDownMenu) + .ButtonContent() + [ + SNew(STextBlock) + .Text_Lambda([this]() { return FromDateInDaysValues[FromDateInDays]; }) + ] + ] + ] + +SHorizontalBox::Slot() + .HAlign(HAlign_Right) .VAlign(VAlign_Center) .AutoWidth() [ - CreateToolBar() + // Button to open the Branches View + SNew(SButton) + .ContentPadding(FMargin(6.0f, 0.0f)) + .ToolTipText(LOCTEXT("PlasticBranchesWindowTooltip", "Open the Branches window.")) +#if ENGINE_MAJOR_VERSION == 5 && ENGINE_MINOR_VERSION >= 1 + .ButtonStyle(FAppStyle::Get(), "SimpleButton") +#else + .ButtonStyle(FEditorStyle::Get(), "SimpleButton") +#endif + .OnClicked_Lambda([]() + { + FPlasticSourceControlModule::Get().GetBranchesWindow().OpenTab(); + return FReply::Handled(); + }) + [ + SNew(SHorizontalBox) + +SHorizontalBox::Slot() + .AutoWidth() + .VAlign(VAlign_Center) + .HAlign(HAlign_Center) + [ + SNew(SImage) +#if ENGINE_MAJOR_VERSION == 5 && ENGINE_MINOR_VERSION >= 1 + .Image(FAppStyle::GetBrush("SourceControl.Branch")) +#else + .Image(FEditorStyle::GetBrush("SourceControl.Branch")) +#endif + ] + +SHorizontalBox::Slot() + .AutoWidth() + .VAlign(VAlign_Center) + .Padding(5.0f, 0.0f, 0.0f, 0.0f) + [ + SNew(STextBlock) +#if ENGINE_MAJOR_VERSION == 5 && ENGINE_MINOR_VERSION >= 1 + .TextStyle(&FAppStyle::Get().GetWidgetStyle("NormalText")) +#else + .TextStyle(&FEditorStyle::Get().GetWidgetStyle("NormalText")) +#endif + .Text(LOCTEXT("PlasticBranchesWindow", "Branches")) + ] + ] ] - +SHorizontalBox::Slot() - .MaxWidth(10.0f) + ] + ] + +SVerticalBox::Slot() // The main content: the splitter with the list of changesets, and the list of files in the selected changeset + [ + SNew(SSplitter) + .Orientation(EOrientation::Orient_Horizontal) + .ResizeMode(ESplitterResizeMode::FixedPosition) + + // Left slot: Changesets area. + +SSplitter::Slot() + .Resizable(true) + .SizeRule(SSplitter::FractionOfParent) + .Value(ChangesetAreaRatio) + [ + SNew(SVerticalBox) + +SVerticalBox::Slot() + .Padding(5.0f) + .AutoHeight() [ - SNew(SSpacer) + CreateChangesetsListView() ] - +SHorizontalBox::Slot() + +SVerticalBox::Slot() .VAlign(VAlign_Center) - .MaxWidth(300.0f) + .HAlign(HAlign_Center) + .FillHeight(1.0f) [ - SAssignNew(FileSearchBox, SSearchBox) - .HintText(LOCTEXT("SearchChangesets", "Search changesets")) - .ToolTipText(LOCTEXT("PlasticChangesetsSearch_Tooltip", "Filter the list of changesets by keyword.")) - .OnTextChanged(this, &SPlasticSourceControlChangesetsWidget::OnSearchTextChanged) + // Text to display when there is no changesets displayed + SNew(STextBlock) + .Text(LOCTEXT("NoChangeset", "There is no changeset to display.")) + .Visibility_Lambda([this]() { return SourceControlChangesets.Num() ? EVisibility::Collapsed : EVisibility::Visible; }) ] - +SHorizontalBox::Slot() + ] + + // Right slot: Files associated to the selected changeset. + +SSplitter::Slot() + .Resizable(true) + .SizeRule(SSplitter::FractionOfParent) + .Value(FileAreaRatio) + [ + SNew(SVerticalBox) + +SVerticalBox::Slot() + .Padding(5.0f) + .AutoHeight() + [ + SAssignNew(FilesSearchBox, SSearchBox) + .MinDesiredWidth(200.0f) + .HintText(LOCTEXT("SearchFiles", "Search the files")) + .ToolTipText(LOCTEXT("PlasticFilesSearch_Tooltip", "Filter the list of files changed by keyword.")) + .OnTextChanged(this, &SPlasticSourceControlChangesetsWidget::OnFilesSearchTextChanged) + ] + +SVerticalBox::Slot() + .AutoHeight() + [ + CreateFilesListView() + ] + +SVerticalBox::Slot() .VAlign(VAlign_Center) - .MaxWidth(125.0f) - .Padding(10.f, 0.f) + .HAlign(HAlign_Center) + .FillHeight(1.0f) [ - SNew(SComboButton) - .ToolTipText(LOCTEXT("PlasticChangesetesDate_Tooltip", "Filter the list of changesets by date of creation.")) - .OnGetMenuContent(this, &SPlasticSourceControlChangesetsWidget::BuildFromDateDropDownMenu) - .ButtonContent() - [ - SNew(STextBlock) - .Text_Lambda([this]() { return FromDateInDaysValues[FromDateInDays]; }) - ] + // Text to display when there is no changeset selected + SNew(STextBlock) + .Text(LOCTEXT("NoChangesetSelected", "Select a changeset from the left panel to see its files.")) + .Visibility_Lambda([this]() { return SourceControlChangesets.IsEmpty() || SourceSelectedChangeset ? EVisibility::Collapsed : EVisibility::Visible; }) ] ] ] - +SVerticalBox::Slot() // The main content: the list of changesets - [ - CreateContentPanel() - ] +SVerticalBox::Slot() // Status bar (Always visible) .AutoHeight() [ @@ -147,8 +270,7 @@ TSharedRef SPlasticSourceControlChangesetsWidget::CreateToolBar() #endif ToolBarBuilder.AddToolBarButton( - FUIAction( - FExecuteAction::CreateLambda([this]() { RequestChangesetsRefresh(); })), + FUIAction(FExecuteAction::CreateLambda([this]() { bShouldRefresh = true; })), NAME_None, LOCTEXT("SourceControl_RefreshButton", "Refresh"), LOCTEXT("SourceControl_RefreshButton_Tooltip", "Refreshes changesets from revision control provider."), @@ -161,7 +283,7 @@ TSharedRef SPlasticSourceControlChangesetsWidget::CreateToolBar() return ToolBarBuilder.MakeWidget(); } -TSharedRef SPlasticSourceControlChangesetsWidget::CreateContentPanel() +TSharedRef SPlasticSourceControlChangesetsWidget::CreateChangesetsListView() { // Inspired by Engine\Source\Editor\SourceControlWindows\Private\SSourceControlChangelists.cpp // TSharedRef> SSourceControlChangelistsWidget::CreateChangelistFilesView() @@ -169,19 +291,19 @@ TSharedRef SPlasticSourceControlChangesetsWidget::CreateContentPanel() UPlasticSourceControlProjectSettings* Settings = GetMutableDefault(); if (!Settings->bShowChangesetCreatedByColumn) { - HiddenColumnsList.Add(PlasticSourceControlChangesetsListViewColumn::CreatedBy::Id()); + ChangesetsHiddenColumnsList.Add(PlasticSourceControlChangesetsListViewColumn::CreatedBy::Id()); } if (!Settings->bShowChangesetDateColumn) { - HiddenColumnsList.Add(PlasticSourceControlChangesetsListViewColumn::Date::Id()); + ChangesetsHiddenColumnsList.Add(PlasticSourceControlChangesetsListViewColumn::Date::Id()); } if (!Settings->bShowChangesetCommentColumn) { - HiddenColumnsList.Add(PlasticSourceControlChangesetsListViewColumn::Comment::Id()); + ChangesetsHiddenColumnsList.Add(PlasticSourceControlChangesetsListViewColumn::Comment::Id()); } if (!Settings->bShowChangesetBranchColumn) { - HiddenColumnsList.Add(PlasticSourceControlChangesetsListViewColumn::Branch::Id()); + ChangesetsHiddenColumnsList.Add(PlasticSourceControlChangesetsListViewColumn::Branch::Id()); } TSharedRef> ChangesetView = SNew(SListView) @@ -189,6 +311,7 @@ TSharedRef SPlasticSourceControlChangesetsWidget::CreateContentPanel() .ListItemsSource(&ChangesetRows) .OnGenerateRow(this, &SPlasticSourceControlChangesetsWidget::OnGenerateRow) .SelectionMode(ESelectionMode::Multi) + .OnSelectionChanged(this, &SPlasticSourceControlChangesetsWidget::OnSelectionChanged) .OnContextMenuOpening(this, &SPlasticSourceControlChangesetsWidget::OnOpenContextMenu) .OnMouseButtonDoubleClick(this, &SPlasticSourceControlChangesetsWidget::OnItemDoubleClicked) .OnItemToString_Debug_Lambda([this](FPlasticSourceControlChangesetRef Changeset) { return FString::FromInt(Changeset->ChangesetId); }) @@ -196,7 +319,7 @@ TSharedRef SPlasticSourceControlChangesetsWidget::CreateContentPanel() ( SNew(SHeaderRow) .CanSelectGeneratedColumn(true) - .HiddenColumnsList(HiddenColumnsList) + .HiddenColumnsList(ChangesetsHiddenColumnsList) .OnHiddenColumnsListChanged(this, &SPlasticSourceControlChangesetsWidget::OnHiddenColumnsListChanged) +SHeaderRow::Column(PlasticSourceControlChangesetsListViewColumn::ChangesetId::Id()) @@ -204,41 +327,41 @@ TSharedRef SPlasticSourceControlChangesetsWidget::CreateContentPanel() .DefaultTooltip(PlasticSourceControlChangesetsListViewColumn::ChangesetId::GetToolTipText()) .ShouldGenerateWidget(true) // Ensure the column cannot be hidden (grayed out in the show/hide drop down menu) .FillWidth(0.6f) - .SortPriority(this, &SPlasticSourceControlChangesetsWidget::GetColumnSortPriority, PlasticSourceControlChangesetsListViewColumn::ChangesetId::Id()) - .SortMode(this, &SPlasticSourceControlChangesetsWidget::GetColumnSortMode, PlasticSourceControlChangesetsListViewColumn::ChangesetId::Id()) - .OnSort(this, &SPlasticSourceControlChangesetsWidget::OnColumnSortModeChanged) + .SortPriority(this, &SPlasticSourceControlChangesetsWidget::GetChangesetsColumnSortPriority, PlasticSourceControlChangesetsListViewColumn::ChangesetId::Id()) + .SortMode(this, &SPlasticSourceControlChangesetsWidget::GetChangesetsColumnSortMode, PlasticSourceControlChangesetsListViewColumn::ChangesetId::Id()) + .OnSort(this, &SPlasticSourceControlChangesetsWidget::OnChangesetsColumnSortModeChanged) +SHeaderRow::Column(PlasticSourceControlChangesetsListViewColumn::CreatedBy::Id()) .DefaultLabel(PlasticSourceControlChangesetsListViewColumn::CreatedBy::GetDisplayText()) .DefaultTooltip(PlasticSourceControlChangesetsListViewColumn::CreatedBy::GetToolTipText()) .FillWidth(2.5f) - .SortPriority(this, &SPlasticSourceControlChangesetsWidget::GetColumnSortPriority, PlasticSourceControlChangesetsListViewColumn::CreatedBy::Id()) - .SortMode(this, &SPlasticSourceControlChangesetsWidget::GetColumnSortMode, PlasticSourceControlChangesetsListViewColumn::CreatedBy::Id()) - .OnSort(this, &SPlasticSourceControlChangesetsWidget::OnColumnSortModeChanged) + .SortPriority(this, &SPlasticSourceControlChangesetsWidget::GetChangesetsColumnSortPriority, PlasticSourceControlChangesetsListViewColumn::CreatedBy::Id()) + .SortMode(this, &SPlasticSourceControlChangesetsWidget::GetChangesetsColumnSortMode, PlasticSourceControlChangesetsListViewColumn::CreatedBy::Id()) + .OnSort(this, &SPlasticSourceControlChangesetsWidget::OnChangesetsColumnSortModeChanged) +SHeaderRow::Column(PlasticSourceControlChangesetsListViewColumn::Date::Id()) .DefaultLabel(PlasticSourceControlChangesetsListViewColumn::Date::GetDisplayText()) .DefaultTooltip(PlasticSourceControlChangesetsListViewColumn::Date::GetToolTipText()) - .FillWidth(1.5f) - .SortPriority(this, &SPlasticSourceControlChangesetsWidget::GetColumnSortPriority, PlasticSourceControlChangesetsListViewColumn::Date::Id()) - .SortMode(this, &SPlasticSourceControlChangesetsWidget::GetColumnSortMode, PlasticSourceControlChangesetsListViewColumn::Date::Id()) - .OnSort(this, &SPlasticSourceControlChangesetsWidget::OnColumnSortModeChanged) + .FillWidth(2.0f) + .SortPriority(this, &SPlasticSourceControlChangesetsWidget::GetChangesetsColumnSortPriority, PlasticSourceControlChangesetsListViewColumn::Date::Id()) + .SortMode(this, &SPlasticSourceControlChangesetsWidget::GetChangesetsColumnSortMode, PlasticSourceControlChangesetsListViewColumn::Date::Id()) + .OnSort(this, &SPlasticSourceControlChangesetsWidget::OnChangesetsColumnSortModeChanged) +SHeaderRow::Column(PlasticSourceControlChangesetsListViewColumn::Comment::Id()) .DefaultLabel(PlasticSourceControlChangesetsListViewColumn::Comment::GetDisplayText()) .DefaultTooltip(PlasticSourceControlChangesetsListViewColumn::Comment::GetToolTipText()) .FillWidth(5.0f) - .SortPriority(this, &SPlasticSourceControlChangesetsWidget::GetColumnSortPriority, PlasticSourceControlChangesetsListViewColumn::Comment::Id()) - .SortMode(this, &SPlasticSourceControlChangesetsWidget::GetColumnSortMode, PlasticSourceControlChangesetsListViewColumn::Comment::Id()) - .OnSort(this, &SPlasticSourceControlChangesetsWidget::OnColumnSortModeChanged) + .SortPriority(this, &SPlasticSourceControlChangesetsWidget::GetChangesetsColumnSortPriority, PlasticSourceControlChangesetsListViewColumn::Comment::Id()) + .SortMode(this, &SPlasticSourceControlChangesetsWidget::GetChangesetsColumnSortMode, PlasticSourceControlChangesetsListViewColumn::Comment::Id()) + .OnSort(this, &SPlasticSourceControlChangesetsWidget::OnChangesetsColumnSortModeChanged) +SHeaderRow::Column(PlasticSourceControlChangesetsListViewColumn::Branch::Id()) .DefaultLabel(PlasticSourceControlChangesetsListViewColumn::Branch::GetDisplayText()) .DefaultTooltip(PlasticSourceControlChangesetsListViewColumn::Branch::GetToolTipText()) .FillWidth(2.0f) - .SortPriority(this, &SPlasticSourceControlChangesetsWidget::GetColumnSortPriority, PlasticSourceControlChangesetsListViewColumn::Branch::Id()) - .SortMode(this, &SPlasticSourceControlChangesetsWidget::GetColumnSortMode, PlasticSourceControlChangesetsListViewColumn::Branch::Id()) - .OnSort(this, &SPlasticSourceControlChangesetsWidget::OnColumnSortModeChanged) + .SortPriority(this, &SPlasticSourceControlChangesetsWidget::GetChangesetsColumnSortPriority, PlasticSourceControlChangesetsListViewColumn::Branch::Id()) + .SortMode(this, &SPlasticSourceControlChangesetsWidget::GetChangesetsColumnSortMode, PlasticSourceControlChangesetsListViewColumn::Branch::Id()) + .OnSort(this, &SPlasticSourceControlChangesetsWidget::OnChangesetsColumnSortModeChanged) ); ChangesetsListView = ChangesetView; @@ -246,13 +369,85 @@ TSharedRef SPlasticSourceControlChangesetsWidget::CreateContentPanel() return ChangesetView; } +TSharedRef SPlasticSourceControlChangesetsWidget::CreateFilesListView() +{ + // Note: array of file States, each with one Revision for Diffing (like for Files and ShelvedFiles in FPlasticSourceControlChangelist) + TSharedRef> FilesView = SNew(SListView) + .ListItemsSource(&FileRows) + .OnGenerateRow(this, &SPlasticSourceControlChangesetsWidget::OnGenerateRow) + .SelectionMode(ESelectionMode::None) + .OnItemToString_Debug_Lambda([this](FPlasticSourceControlStateRef FileState) { return FileState->LocalFilename; }) + .HeaderRow + ( + SNew(SHeaderRow) + .CanSelectGeneratedColumn(true) + + +SHeaderRow::Column(PlasticSourceControlChangesetFilesListViewColumn::Icon::Id()) + .DefaultLabel(PlasticSourceControlChangesetFilesListViewColumn::Icon::GetDisplayText()) // Displayed in the drop down menu to show/hide columns + .DefaultTooltip(PlasticSourceControlChangesetFilesListViewColumn::Icon::GetToolTipText()) + .ShouldGenerateWidget(true) // Ensure the column cannot be hidden (grayed out in the show/hide drop down menu) + .FillSized(18) + .HeaderContentPadding(FMargin(0)) + .SortPriority(this, &SPlasticSourceControlChangesetsWidget::GetFilesColumnSortPriority, PlasticSourceControlChangesetFilesListViewColumn::Icon::Id()) + .SortMode(this, &SPlasticSourceControlChangesetsWidget::GetFilesColumnSortMode, PlasticSourceControlChangesetFilesListViewColumn::Icon::Id()) + .OnSort(this, &SPlasticSourceControlChangesetsWidget::OnFilesColumnSortModeChanged) + [ + SNew(SHorizontalBox) + +SHorizontalBox::Slot() + .Padding(1, 0) + [ + SNew(SBox) + .WidthOverride(16) + .HeightOverride(16) + .HAlign(HAlign_Center) + .VAlign(VAlign_Center) + .Visibility_Lambda([this](){ return GetFilesColumnSortMode(PlasticSourceControlChangesetFilesListViewColumn::Icon::Id()) == EColumnSortMode::None ? EVisibility::Visible : EVisibility::Collapsed; }) + [ + SNew(SImage) + .Image(FAppStyle::Get().GetBrush("SourceControl.ChangelistsTab")) + .ColorAndOpacity(FSlateColor::UseSubduedForeground()) + ] + ] + ] + + +SHeaderRow::Column(PlasticSourceControlChangesetFilesListViewColumn::Name::Id()) + .DefaultLabel(PlasticSourceControlChangesetFilesListViewColumn::Name::GetDisplayText()) + .DefaultTooltip(PlasticSourceControlChangesetFilesListViewColumn::Name::GetToolTipText()) + .ShouldGenerateWidget(true) // Ensure the column cannot be hidden (grayed out in the show/hide drop down menu) + .FillWidth(0.7f) + .SortPriority(this, &SPlasticSourceControlChangesetsWidget::GetFilesColumnSortPriority, PlasticSourceControlChangesetFilesListViewColumn::Name::Id()) + .SortMode(this, &SPlasticSourceControlChangesetsWidget::GetFilesColumnSortMode, PlasticSourceControlChangesetFilesListViewColumn::Name::Id()) + .OnSort(this, &SPlasticSourceControlChangesetsWidget::OnFilesColumnSortModeChanged) + + +SHeaderRow::Column(PlasticSourceControlChangesetFilesListViewColumn::Path::Id()) + .DefaultLabel(PlasticSourceControlChangesetFilesListViewColumn::Path::GetDisplayText()) + .DefaultTooltip(PlasticSourceControlChangesetFilesListViewColumn::Path::GetToolTipText()) + .ShouldGenerateWidget(true) // Ensure the column cannot be hidden (grayed out in the show/hide drop down menu) + .FillWidth(2.0f) + .SortPriority(this, &SPlasticSourceControlChangesetsWidget::GetFilesColumnSortPriority, PlasticSourceControlChangesetFilesListViewColumn::Path::Id()) + .SortMode(this, &SPlasticSourceControlChangesetsWidget::GetFilesColumnSortMode, PlasticSourceControlChangesetFilesListViewColumn::Path::Id()) + .OnSort(this, &SPlasticSourceControlChangesetsWidget::OnFilesColumnSortModeChanged) + ); + + FilesListView = FilesView; + + return FilesView; +} + TSharedRef SPlasticSourceControlChangesetsWidget::OnGenerateRow(FPlasticSourceControlChangesetRef InChangeset, const TSharedRef& OwnerTable) { const bool bIsCurrentChangeset = InChangeset->ChangesetId == CurrentChangesetId; return SNew(SPlasticSourceControlChangesetRow, OwnerTable) .ChangesetToVisualize(InChangeset) .bIsCurrentChangeset(bIsCurrentChangeset) - .HighlightText_Lambda([this]() { return FileSearchBox->GetText(); }); + .HighlightText_Lambda([this]() { return ChangesetsSearchBox->GetText(); }); +} + +TSharedRef SPlasticSourceControlChangesetsWidget::OnGenerateRow(FPlasticSourceControlStateRef InFile, const TSharedRef& OwnerTable) +{ + return SNew(SPlasticSourceControlChangesetFileRow, OwnerTable) + .FileToVisualize(InFile) + .HighlightText_Lambda([this]() { return FilesSearchBox->GetText(); }); } void SPlasticSourceControlChangesetsWidget::OnHiddenColumnsListChanged() @@ -289,10 +484,16 @@ void SPlasticSourceControlChangesetsWidget::OnHiddenColumnsListChanged() } } -void SPlasticSourceControlChangesetsWidget::OnSearchTextChanged(const FText& InFilterText) +void SPlasticSourceControlChangesetsWidget::OnChangesetsSearchTextChanged(const FText& InFilterText) { - SearchTextFilter->SetRawFilterText(InFilterText); - FileSearchBox->SetError(SearchTextFilter->GetFilterErrorText()); + ChangesetsSearchTextFilter->SetRawFilterText(InFilterText); + ChangesetsSearchBox->SetError(ChangesetsSearchTextFilter->GetFilterErrorText()); +} + +void SPlasticSourceControlChangesetsWidget::OnFilesSearchTextChanged(const FText& InFilterText) +{ + FilesSearchTextFilter->SetRawFilterText(InFilterText); + FilesSearchBox->SetError(FilesSearchTextFilter->GetFilterErrorText()); } void SPlasticSourceControlChangesetsWidget::PopulateItemSearchStrings(const FPlasticSourceControlChangeset& InItem, TArray& OutStrings) @@ -300,11 +501,15 @@ void SPlasticSourceControlChangesetsWidget::PopulateItemSearchStrings(const FPla InItem.PopulateSearchString(OutStrings); } +void SPlasticSourceControlChangesetsWidget::PopulateItemSearchStrings(const FPlasticSourceControlState& InItem, TArray& OutStrings) +{ + InItem.PopulateSearchString(OutStrings); +} + void SPlasticSourceControlChangesetsWidget::OnFromDateChanged(int32 InFromDateInDays) { FromDateInDays = InFromDateInDays; - - RequestChangesetsRefresh(); + bShouldRefresh = true; } TSharedRef SPlasticSourceControlChangesetsWidget::BuildFromDateDropDownMenu() @@ -320,35 +525,63 @@ TSharedRef SPlasticSourceControlChangesetsWidget::BuildFromDateDropDown return MenuBuilder.MakeWidget(); } -void SPlasticSourceControlChangesetsWidget::OnRefreshUI() +void SPlasticSourceControlChangesetsWidget::OnChangesetsRefreshUI() { - TRACE_CPUPROFILER_EVENT_SCOPE(SPlasticSourceControlChangesetsWidget::OnRefreshUI); + TRACE_CPUPROFILER_EVENT_SCOPE(SPlasticSourceControlChangesetsWidget::OnChangesetsRefreshUI); const int32 ItemCount = SourceControlChangesets.Num(); ChangesetRows.Empty(ItemCount); for (int32 ItemIndex = 0; ItemIndex < ItemCount; ++ItemIndex) { const FPlasticSourceControlChangesetRef& Item = SourceControlChangesets[ItemIndex]; - if (SearchTextFilter->PassesFilter(Item.Get())) + if (ChangesetsSearchTextFilter->PassesFilter(Item.Get())) { ChangesetRows.Emplace(Item); } } - if (GetListView()) + if (ChangesetsListView) + { + SortChangesetsView(); + ChangesetsListView->RequestListRefresh(); + } + + // And also refresh the list of files + OnFilesRefreshUI(); +} + +void SPlasticSourceControlChangesetsWidget::OnFilesRefreshUI() +{ + TRACE_CPUPROFILER_EVENT_SCOPE(SPlasticSourceControlChangesetsWidget::OnFilesRefreshUI); + + if (SourceSelectedChangeset.IsValid()) + { + const int32 ItemCount = SourceSelectedChangeset->Files.Num(); + FileRows.Empty(ItemCount); + for (int32 ItemIndex = 0; ItemIndex < ItemCount; ++ItemIndex) + { + const FPlasticSourceControlStateRef& Item = SourceSelectedChangeset->Files[ItemIndex]; + if (FilesSearchTextFilter->PassesFilter(Item.Get())) + { + FileRows.Emplace(Item); + } + } + } + + if (FilesListView) { - SortChangesetView(); - GetListView()->RequestListRefresh(); + SortFilesView(); + FilesListView->RequestListRefresh(); } } -EColumnSortPriority::Type SPlasticSourceControlChangesetsWidget::GetColumnSortPriority(const FName InColumnId) const +EColumnSortPriority::Type SPlasticSourceControlChangesetsWidget::GetChangesetsColumnSortPriority(const FName InColumnId) const { - if (InColumnId == PrimarySortedColumn) + if (InColumnId == ChangesetsPrimarySortedColumn) { return EColumnSortPriority::Primary; } - else if (InColumnId == SecondarySortedColumn) + else if (InColumnId == ChangesetsSecondarySortedColumn) { return EColumnSortPriority::Secondary; } @@ -356,51 +589,51 @@ EColumnSortPriority::Type SPlasticSourceControlChangesetsWidget::GetColumnSortPr return EColumnSortPriority::Max; // No specific priority. } -EColumnSortMode::Type SPlasticSourceControlChangesetsWidget::GetColumnSortMode(const FName InColumnId) const +EColumnSortMode::Type SPlasticSourceControlChangesetsWidget::GetChangesetsColumnSortMode(const FName InColumnId) const { - if (InColumnId == PrimarySortedColumn) + if (InColumnId == ChangesetsPrimarySortedColumn) { - return PrimarySortMode; + return ChangesetsPrimarySortMode; } - else if (InColumnId == SecondarySortedColumn) + else if (InColumnId == ChangesetsSecondarySortedColumn) { - return SecondarySortMode; + return ChangesetsSecondarySortMode; } return EColumnSortMode::None; } -void SPlasticSourceControlChangesetsWidget::OnColumnSortModeChanged(const EColumnSortPriority::Type InSortPriority, const FName& InColumnId, const EColumnSortMode::Type InSortMode) +void SPlasticSourceControlChangesetsWidget::OnChangesetsColumnSortModeChanged(const EColumnSortPriority::Type InSortPriority, const FName& InColumnId, const EColumnSortMode::Type InSortMode) { if (InSortPriority == EColumnSortPriority::Primary) { - PrimarySortedColumn = InColumnId; - PrimarySortMode = InSortMode; + ChangesetsPrimarySortedColumn = InColumnId; + ChangesetsPrimarySortMode = InSortMode; - if (InColumnId == SecondarySortedColumn) // Cannot be primary and secondary at the same time. + if (InColumnId == ChangesetsSecondarySortedColumn) // Cannot be primary and secondary at the same time. { - SecondarySortedColumn = FName(); - SecondarySortMode = EColumnSortMode::None; + ChangesetsSecondarySortedColumn = FName(); + ChangesetsSecondarySortMode = EColumnSortMode::None; } } else if (InSortPriority == EColumnSortPriority::Secondary) { - SecondarySortedColumn = InColumnId; - SecondarySortMode = InSortMode; + ChangesetsSecondarySortedColumn = InColumnId; + ChangesetsSecondarySortMode = InSortMode; } - if (GetListView()) + if (ChangesetsListView) { - SortChangesetView(); - GetListView()->RequestListRefresh(); + SortChangesetsView(); + ChangesetsListView->RequestListRefresh(); } } -void SPlasticSourceControlChangesetsWidget::SortChangesetView() +void SPlasticSourceControlChangesetsWidget::SortChangesetsView() { - TRACE_CPUPROFILER_EVENT_SCOPE(SPlasticSourceControlChangesetsWidget::SortChangesetView); + TRACE_CPUPROFILER_EVENT_SCOPE(SPlasticSourceControlChangesetsWidget::SortChangesetsView); - if (PrimarySortedColumn.IsNone() || ChangesetRows.Num() == 0) + if (ChangesetsPrimarySortedColumn.IsNone() || ChangesetRows.Num() == 0) { return; // No column selected for sorting or nothing to sort. } @@ -459,14 +692,14 @@ void SPlasticSourceControlChangesetsWidget::SortChangesetView() }; }; - TFunction PrimaryCompare = GetCompareFunc(PrimarySortedColumn); + TFunction PrimaryCompare = GetCompareFunc(ChangesetsPrimarySortedColumn); TFunction SecondaryCompare; - if (!SecondarySortedColumn.IsNone()) + if (!ChangesetsSecondarySortedColumn.IsNone()) { - SecondaryCompare = GetCompareFunc(SecondarySortedColumn); + SecondaryCompare = GetCompareFunc(ChangesetsSecondarySortedColumn); } - if (PrimarySortMode == EColumnSortMode::Ascending) + if (ChangesetsPrimarySortMode == EColumnSortMode::Ascending) { // NOTE: StableSort() would give a better experience when the sorted columns(s) has the same values and new values gets added, but it is slower // with large changelists (7600 items was about 1.8x slower in average measured with Unreal Insight). Because this code runs in the main @@ -482,7 +715,7 @@ void SPlasticSourceControlChangesetsWidget::SortChangesetView() { return false; } - else if (SecondarySortMode == EColumnSortMode::Ascending) + else if (ChangesetsSecondarySortMode == EColumnSortMode::Ascending) { return SecondaryCompare(static_cast(Lhs.Get()), static_cast(Rhs.Get())) < 0; } @@ -505,7 +738,7 @@ void SPlasticSourceControlChangesetsWidget::SortChangesetView() { return false; } - else if (SecondarySortMode == EColumnSortMode::Ascending) + else if (ChangesetsSecondarySortMode == EColumnSortMode::Ascending) { return SecondaryCompare(static_cast(Lhs.Get()), static_cast(Rhs.Get())) < 0; } @@ -517,6 +750,165 @@ void SPlasticSourceControlChangesetsWidget::SortChangesetView() } } +EColumnSortPriority::Type SPlasticSourceControlChangesetsWidget::GetFilesColumnSortPriority(const FName InColumnId) const +{ + if (InColumnId == FilesPrimarySortedColumn) + { + return EColumnSortPriority::Primary; + } + else if (InColumnId == FilesSecondarySortedColumn) + { + return EColumnSortPriority::Secondary; + } + + return EColumnSortPriority::Max; // No specific priority. +} + +EColumnSortMode::Type SPlasticSourceControlChangesetsWidget::GetFilesColumnSortMode(const FName InColumnId) const +{ + if (InColumnId == FilesPrimarySortedColumn) + { + return FilesPrimarySortMode; + } + else if (InColumnId == FilesSecondarySortedColumn) + { + return FilesSecondarySortMode; + } + + return EColumnSortMode::None; +} + +void SPlasticSourceControlChangesetsWidget::OnFilesColumnSortModeChanged(const EColumnSortPriority::Type InSortPriority, const FName& InColumnId, const EColumnSortMode::Type InSortMode) +{ + if (InSortPriority == EColumnSortPriority::Primary) + { + FilesPrimarySortedColumn = InColumnId; + FilesPrimarySortMode = InSortMode; + + if (InColumnId == FilesSecondarySortedColumn) // Cannot be primary and secondary at the same time. + { + FilesSecondarySortedColumn = FName(); + FilesSecondarySortMode = EColumnSortMode::None; + } + } + else if (InSortPriority == EColumnSortPriority::Secondary) + { + FilesSecondarySortedColumn = InColumnId; + FilesSecondarySortMode = InSortMode; + } + + if (FilesListView) + { + SortFilesView(); + FilesListView->RequestListRefresh(); + } +} + +void SPlasticSourceControlChangesetsWidget::SortFilesView() +{ + TRACE_CPUPROFILER_EVENT_SCOPE(SPlasticSourceControlChangesetsWidget::SortFilesView); + + if (FilesPrimarySortedColumn.IsNone() || FileRows.Num() == 0) + { + return; // No column selected for sorting or nothing to sort. + } + + auto CompareIcons = [](const FPlasticSourceControlState* Lhs, const FPlasticSourceControlState* Rhs) + { + const int32 LhsVal = static_cast(Lhs->WorkspaceState); + const int32 RhsVal = static_cast(Rhs->WorkspaceState); + return LhsVal < RhsVal ? -1 : (LhsVal == RhsVal ? 0 : 1); + }; + + auto CompareNames = [](const FPlasticSourceControlState* Lhs, const FPlasticSourceControlState* Rhs) + { + return FCString::Stricmp(*FPaths::GetBaseFilename(Lhs->LocalFilename), *FPaths::GetBaseFilename(Rhs->LocalFilename)); + }; + + auto ComparePaths = [](const FPlasticSourceControlState* Lhs, const FPlasticSourceControlState* Rhs) + { + return FCString::Stricmp(*Lhs->LocalFilename, *Rhs->LocalFilename); + }; + + auto GetCompareFunc = [&](const FName& ColumnId) + { + if (ColumnId == PlasticSourceControlChangesetFilesListViewColumn::Icon::Id()) + { + return TFunction(CompareIcons); + } + else if (ColumnId == PlasticSourceControlChangesetFilesListViewColumn::Name::Id()) + { + return TFunction(CompareNames); + } + else if (ColumnId == PlasticSourceControlChangesetFilesListViewColumn::Path::Id()) + { + return TFunction(ComparePaths); + } + else + { + checkNoEntry(); + return TFunction(); + }; + }; + + TFunction PrimaryCompare = GetCompareFunc(FilesPrimarySortedColumn); + TFunction SecondaryCompare; + if (!FilesSecondarySortedColumn.IsNone()) + { + SecondaryCompare = GetCompareFunc(FilesSecondarySortedColumn); + } + + if (FilesPrimarySortMode == EColumnSortMode::Ascending) + { + // NOTE: StableSort() would give a better experience when the sorted columns(s) has the same values and new values gets added, but it is slower + // with large changelists (7600 items was about 1.8x slower in average measured with Unreal Insight). Because this code runs in the main + // thread and can be invoked a lot, the trade off went if favor of speed. + FileRows.Sort([this, &PrimaryCompare, &SecondaryCompare](const FPlasticSourceControlStatePtr& Lhs, const FPlasticSourceControlStatePtr& Rhs) + { + int32 Result = PrimaryCompare(static_cast(Lhs.Get()), static_cast(Rhs.Get())); + if (Result < 0) + { + return true; + } + else if (Result > 0 || !SecondaryCompare) + { + return false; + } + else if (FilesSecondarySortMode == EColumnSortMode::Ascending) + { + return SecondaryCompare(static_cast(Lhs.Get()), static_cast(Rhs.Get())) < 0; + } + else + { + return SecondaryCompare(static_cast(Lhs.Get()), static_cast(Rhs.Get())) > 0; + } + }); + } + else + { + FileRows.Sort([this, &PrimaryCompare, &SecondaryCompare](const FPlasticSourceControlStatePtr& Lhs, const FPlasticSourceControlStatePtr& Rhs) + { + int32 Result = PrimaryCompare(static_cast(Lhs.Get()), static_cast(Rhs.Get())); + if (Result > 0) + { + return true; + } + else if (Result < 0 || !SecondaryCompare) + { + return false; + } + else if (FilesSecondarySortMode == EColumnSortMode::Ascending) + { + return SecondaryCompare(static_cast(Lhs.Get()), static_cast(Rhs.Get())) < 0; + } + else + { + return SecondaryCompare(static_cast(Lhs.Get()), static_cast(Rhs.Get())) > 0; + } + }); + } +} + TSharedPtr SPlasticSourceControlChangesetsWidget::OnOpenContextMenu() { const TArray SelectedChangesets = ChangesetsListView->GetSelectedItems(); @@ -776,6 +1168,14 @@ void SPlasticSourceControlChangesetsWidget::Tick(const FGeometry& AllottedGeomet bShouldRefresh = true; } + // Auto refresh at regular intervals + const double CurrentTime = FPlatformTime::Seconds(); + if (CurrentTime - LastRefreshTime > (10 * 60)) + { + LastRefreshTime = CurrentTime; + bShouldRefresh = true; + } + if (bShouldRefresh) { RequestChangesetsRefresh(); @@ -828,17 +1228,39 @@ void SPlasticSourceControlChangesetsWidget::RequestChangesetsRefresh() Provider.Execute(GetChangesetsOperation, EConcurrency::Asynchronous, FSourceControlOperationComplete::CreateSP(this, &SPlasticSourceControlChangesetsWidget::OnGetChangesetsOperationComplete)); } -void SPlasticSourceControlChangesetsWidget::OnGetChangesetsOperationComplete(const FSourceControlOperationRef& InOperation, ECommandResult::Type InResult) +void SPlasticSourceControlChangesetsWidget::RequestGetChangesetFiles(const FPlasticSourceControlChangesetPtr& InSelectedChangeset) { - TRACE_CPUPROFILER_EVENT_SCOPE(SPlasticSourceControlChangesetsWidget::OnGetChangesetsOperationComplete); + if (!ISourceControlModule::Get().IsEnabled() || (!FPlasticSourceControlModule::Get().GetProvider().IsAvailable())) + { + return; + } + + StartRefreshStatus(); - TSharedRef OperationGetChangesets = StaticCastSharedRef(InOperation); - SourceControlChangesets = MoveTemp(OperationGetChangesets->Changesets); + FPlasticSourceControlProvider& Provider = FPlasticSourceControlModule::Get().GetProvider(); + TSharedRef GetChangesetFilesOperation = ISourceControlOperation::Create(); + GetChangesetFilesOperation->Changeset = InSelectedChangeset; + Provider.Execute(GetChangesetFilesOperation, EConcurrency::Asynchronous, FSourceControlOperationComplete::CreateSP(this, &SPlasticSourceControlChangesetsWidget::OnGetChangesetFilesOperationComplete)); +} + +void SPlasticSourceControlChangesetsWidget::OnGetChangesetsOperationComplete(const FSourceControlOperationRef& InOperation, ECommandResult::Type InResult) +{ + TSharedRef GetChangesetsOperation = StaticCastSharedRef(InOperation); + SourceControlChangesets = MoveTemp(GetChangesetsOperation->Changesets); CurrentChangesetId = FPlasticSourceControlModule::Get().GetProvider().GetChangesetNumber(); EndRefreshStatus(); - OnRefreshUI(); + OnChangesetsRefreshUI(); +} + +void SPlasticSourceControlChangesetsWidget::OnGetChangesetFilesOperationComplete(const FSourceControlOperationRef& InOperation, ECommandResult::Type InResult) +{ + TSharedRef GetChangesetFilesOperation = StaticCastSharedRef(InOperation); + GetChangesetFilesOperation->Changeset->Files = MoveTemp(GetChangesetFilesOperation->Files); + + EndRefreshStatus(); + OnFilesRefreshUI(); } void SPlasticSourceControlChangesetsWidget::OnSwitchToBranchOperationComplete(const FSourceControlOperationRef& InOperation, ECommandResult::Type InResult) @@ -849,7 +1271,7 @@ void SPlasticSourceControlChangesetsWidget::OnSwitchToBranchOperationComplete(co TSharedRef SwitchToBranchOperation = StaticCastSharedRef(InOperation); PackageUtils::ReloadPackages(SwitchToBranchOperation->UpdatedFiles); - // Ask for a full refresh of the list of branches (and don't call EndRefreshStatus() yet) + // Ask for a full refresh of the list of changesets (and don't call EndRefreshStatus() yet) bShouldRefresh = true; Notification.RemoveInProgress(); @@ -873,7 +1295,7 @@ void SPlasticSourceControlChangesetsWidget::OnSwitchToChangesetOperationComplete PackageUtils::ReloadPackages(UpdateToChangesetOperation->UpdatedFiles); } - // Ask for a full refresh of the list of branches (and don't call EndRefreshStatus() yet) + // Ask for a full refresh of the list of changesets (and don't call EndRefreshStatus() yet) bShouldRefresh = true; Notification.RemoveInProgress(); @@ -889,9 +1311,15 @@ void SPlasticSourceControlChangesetsWidget::OnSourceControlProviderChanged(ISour if (&NewProvider != &OldProvider) { ChangesetRows.Reset(); - if (GetListView()) + if (ChangesetsListView) + { + ChangesetsListView->RequestListRefresh(); + } + + FileRows.Reset(); + if (FilesListView) { - GetListView()->RequestListRefresh(); + FilesListView->RequestListRefresh(); } } } @@ -899,9 +1327,33 @@ void SPlasticSourceControlChangesetsWidget::OnSourceControlProviderChanged(ISour void SPlasticSourceControlChangesetsWidget::HandleSourceControlStateChanged() { bShouldRefresh = true; - if (GetListView()) + if (ChangesetsListView) + { + ChangesetsListView->RequestListRefresh(); + } + if (FilesListView) { - GetListView()->RequestListRefresh(); + FilesListView->RequestListRefresh(); + } +} + +// on item selected, we could show the list of files changed in the changeset +void SPlasticSourceControlChangesetsWidget::OnSelectionChanged(FPlasticSourceControlChangesetPtr InSelectedChangeset, ESelectInfo::Type SelectInfo) +{ + if (!InSelectedChangeset.IsValid()) + return; + + SourceSelectedChangeset = InSelectedChangeset; + + if (SourceSelectedChangeset->Files.IsEmpty()) + { + // Asynchronously get the list of files changed in the changeset + RequestGetChangesetFiles(SourceSelectedChangeset); + } + else + { + // Just refresh the list of files + OnFilesRefreshUI(); } } @@ -915,16 +1367,19 @@ FReply SPlasticSourceControlChangesetsWidget::OnKeyDown(const FGeometry& MyGeome if (InKeyEvent.GetKey() == EKeys::F5) { // Pressing F5 refreshes the list of changesets - RequestChangesetsRefresh(); + bShouldRefresh = true; return FReply::Handled(); } else if (InKeyEvent.GetKey() == EKeys::Enter) { // Pressing Enter open the diff for the selected changeset (like a double click) - const TArray SelectedChangesets = ChangesetsListView->GetSelectedItems(); - if (SelectedChangesets.Num() == 1) + if (ChangesetsListView) { - OnDiffChangesetClicked(SelectedChangesets[0]); + const TArray SelectedChangesets = ChangesetsListView->GetSelectedItems(); + if (SelectedChangesets.Num() == 1) + { + OnDiffChangesetClicked(SelectedChangesets[0]); + } } return FReply::Handled(); } diff --git a/Source/PlasticSourceControl/Private/SPlasticSourceControlChangesetsWidget.h b/Source/PlasticSourceControl/Private/SPlasticSourceControlChangesetsWidget.h index 9cf4f44d..832dc6fb 100644 --- a/Source/PlasticSourceControl/Private/SPlasticSourceControlChangesetsWidget.h +++ b/Source/PlasticSourceControl/Private/SPlasticSourceControlChangesetsWidget.h @@ -16,6 +16,7 @@ typedef TSharedRef FPlasticSourceControlChangesetRef; typedef TSharedPtr FPlasticSourceControlChangesetPtr; +typedef TSharedRef FPlasticSourceControlStateRef; class SSearchBox; @@ -31,24 +32,34 @@ class SPlasticSourceControlChangesetsWidget : public SCompoundWidget private: TSharedRef CreateToolBar(); - TSharedRef CreateContentPanel(); + TSharedRef CreateChangesetsListView(); + TSharedRef CreateFilesListView(); TSharedRef OnGenerateRow(FPlasticSourceControlChangesetRef InChangeset, const TSharedRef& OwnerTable); + TSharedRef OnGenerateRow(FPlasticSourceControlStateRef InChangeset, const TSharedRef& OwnerTable); void OnHiddenColumnsListChanged(); - void OnSearchTextChanged(const FText& InFilterText); + void OnChangesetsSearchTextChanged(const FText& InFilterText); + void OnFilesSearchTextChanged(const FText& InFilterText); void PopulateItemSearchStrings(const FPlasticSourceControlChangeset& InItem, TArray& OutStrings); + void PopulateItemSearchStrings(const FPlasticSourceControlState& InItem, TArray& OutStrings); TSharedRef BuildFromDateDropDownMenu(); void OnFromDateChanged(int32 InFromDateInDays); - void OnRefreshUI(); + void OnChangesetsRefreshUI(); + void OnFilesRefreshUI(); - EColumnSortPriority::Type GetColumnSortPriority(const FName InColumnId) const; - EColumnSortMode::Type GetColumnSortMode(const FName InColumnId) const; - void OnColumnSortModeChanged(const EColumnSortPriority::Type InSortPriority, const FName& InColumnId, const EColumnSortMode::Type InSortMode); + EColumnSortPriority::Type GetChangesetsColumnSortPriority(const FName InColumnId) const; + EColumnSortMode::Type GetChangesetsColumnSortMode(const FName InColumnId) const; + void OnChangesetsColumnSortModeChanged(const EColumnSortPriority::Type InSortPriority, const FName& InColumnId, const EColumnSortMode::Type InSortMode); - void SortChangesetView(); + EColumnSortPriority::Type GetFilesColumnSortPriority(const FName InColumnId) const; + EColumnSortMode::Type GetFilesColumnSortMode(const FName InColumnId) const; + void OnFilesColumnSortModeChanged(const EColumnSortPriority::Type InSortPriority, const FName& InColumnId, const EColumnSortMode::Type InSortMode); + + void SortChangesetsView(); + void SortFilesView(); TSharedPtr OnOpenContextMenu(); @@ -63,9 +74,11 @@ class SPlasticSourceControlChangesetsWidget : public SCompoundWidget void EndRefreshStatus(); void RequestChangesetsRefresh(); + void RequestGetChangesetFiles(const FPlasticSourceControlChangesetPtr& InSelectedChangeset); /** Source control callbacks */ void OnGetChangesetsOperationComplete(const FSourceControlOperationRef& InOperation, ECommandResult::Type InResult); + void OnGetChangesetFilesOperationComplete(const FSourceControlOperationRef& InOperation, ECommandResult::Type InResult); void OnSwitchToBranchOperationComplete(const FSourceControlOperationRef& InOperation, ECommandResult::Type InResult); void OnSwitchToChangesetOperationComplete(const FSourceControlOperationRef& InOperation, ECommandResult::Type InResult); void OnSourceControlProviderChanged(ISourceControlProvider& OldProvider, ISourceControlProvider& NewProvider); @@ -73,26 +86,29 @@ class SPlasticSourceControlChangesetsWidget : public SCompoundWidget /** Delegate handler for when source control state changes */ void HandleSourceControlStateChanged(); - SListView* GetListView() const - { - return ChangesetsListView.Get(); - } + void OnSelectionChanged(FPlasticSourceControlChangesetPtr InSelectedChangeset, ESelectInfo::Type SelectInfo); /** Double click to diff the selected changeset */ - void OnItemDoubleClicked(FPlasticSourceControlChangesetRef InBranch); + void OnItemDoubleClicked(FPlasticSourceControlChangesetRef InChangeset); /** Interpret F5 and Enter keys */ virtual FReply OnKeyDown(const FGeometry& MyGeometry, const FKeyEvent& InKeyEvent) override; private: - TSharedPtr FileSearchBox; + TSharedPtr ChangesetsSearchBox; + TSharedPtr FilesSearchBox; + + FName ChangesetsPrimarySortedColumn; + FName ChangesetsSecondarySortedColumn; + EColumnSortMode::Type ChangesetsPrimarySortMode = EColumnSortMode::Ascending; + EColumnSortMode::Type ChangesetsSecondarySortMode = EColumnSortMode::None; - FName PrimarySortedColumn; - FName SecondarySortedColumn; - EColumnSortMode::Type PrimarySortMode = EColumnSortMode::Ascending; - EColumnSortMode::Type SecondarySortMode = EColumnSortMode::None; + FName FilesPrimarySortedColumn; + FName FilesSecondarySortedColumn; + EColumnSortMode::Type FilesPrimarySortMode = EColumnSortMode::Ascending; + EColumnSortMode::Type FilesSecondarySortMode = EColumnSortMode::None; - TArray HiddenColumnsList; + TArray ChangesetsHiddenColumnsList; bool bShouldRefresh = false; bool bSourceControlAvailable = false; @@ -100,6 +116,7 @@ class SPlasticSourceControlChangesetsWidget : public SCompoundWidget FText RefreshStatus; bool bIsRefreshing = false; double RefreshStatusStartSecs; + double LastRefreshTime = 0.0; int32 CurrentChangesetId; @@ -107,7 +124,7 @@ class SPlasticSourceControlChangesetsWidget : public SCompoundWidget FNotification Notification; TSharedPtr> ChangesetsListView; - TSharedPtr> SearchTextFilter; + TSharedPtr> ChangesetsSearchTextFilter; TMap FromDateInDaysValues; int32 FromDateInDays = 30; @@ -115,6 +132,12 @@ class SPlasticSourceControlChangesetsWidget : public SCompoundWidget TArray SourceControlChangesets; // Full list from source (filtered by date) TArray ChangesetRows; // Filtered list to display based on the search text filter + TSharedPtr> FilesListView; + TSharedPtr> FilesSearchTextFilter; + + FPlasticSourceControlChangesetPtr SourceSelectedChangeset; // Current selected changeset from source control if any, with full list of files + TArray FileRows; // Filtered list to display based on the search text filter + /** Delegate handle for the HandleSourceControlStateChanged function callback */ FDelegateHandle SourceControlStateChangedDelegateHandle; }; diff --git a/Source/PlasticSourceControl/Private/SPlasticSourceControlLocksWidget.cpp b/Source/PlasticSourceControl/Private/SPlasticSourceControlLocksWidget.cpp index a75aac25..7d3b91f4 100644 --- a/Source/PlasticSourceControl/Private/SPlasticSourceControlLocksWidget.cpp +++ b/Source/PlasticSourceControl/Private/SPlasticSourceControlLocksWidget.cpp @@ -27,6 +27,7 @@ #else #include "EditorStyleSet.h" #endif +#include "Widgets/Images/SImage.h" #include "Widgets/Input/SComboButton.h" #include "Widgets/Input/SSearchBox.h" #include "Widgets/Layout/SSpacer.h" @@ -84,7 +85,7 @@ void SPlasticSourceControlLocksWidget::Construct(const FArguments& InArgs) .VAlign(VAlign_Center) .MaxWidth(300.0f) [ - SAssignNew(FileSearchBox, SSearchBox) + SAssignNew(LockSearchBox, SSearchBox) .HintText(LOCTEXT("SearchLocks", "Search Locks")) .ToolTipText(LOCTEXT("PlasticLocksSearch_Tooltip", "Filter the list of locks by keyword.")) .OnTextChanged(this, &SPlasticSourceControlLocksWidget::OnSearchTextChanged) @@ -141,7 +142,23 @@ void SPlasticSourceControlLocksWidget::Construct(const FArguments& InArgs) ] +SVerticalBox::Slot() // The main content: the list of locks [ - CreateContentPanel() + SNew(SVerticalBox) + +SVerticalBox::Slot() + .Padding(5.0f) + .AutoHeight() + [ + CreateContentPanel() + ] + +SVerticalBox::Slot() + .VAlign(VAlign_Center) + .HAlign(HAlign_Center) + .FillHeight(1.0f) + [ + // Text to display when there is no lock displayed + SNew(STextBlock) + .Text(LOCTEXT("NoLock", "There is no lock to display.")) + .Visibility_Lambda([this]() { return SourceControlLocks.Num() ? EVisibility::Collapsed : EVisibility::Visible; }) + ] ] +SVerticalBox::Slot() // Status bar (Always visible) .AutoHeight() @@ -179,8 +196,11 @@ TSharedRef SPlasticSourceControlLocksWidget::CreateToolBar() #endif ToolBarBuilder.AddToolBarButton( - FUIAction( - FExecuteAction::CreateLambda([this]() { RequestLocksRefresh(true); })), + FUIAction(FExecuteAction::CreateLambda([this]() + { + bShouldRefresh = true; + bShouldInvalidateLocksCache = true; + })), NAME_None, LOCTEXT("SourceControl_RefreshButton", "Refresh"), LOCTEXT("SourceControl_RefreshButton_Tooltip", "Refreshes locks from revision control provider."), @@ -308,7 +328,7 @@ TSharedRef SPlasticSourceControlLocksWidget::OnGenerateRow(FPlasticSo { return SNew(SPlasticSourceControlLockRow, OwnerTable) .LockToVisualize(InLock) - .HighlightText_Lambda([this]() { return FileSearchBox->GetText(); }); + .HighlightText_Lambda([this]() { return LockSearchBox->GetText(); }); } void SPlasticSourceControlLocksWidget::OnHiddenColumnsListChanged() @@ -348,7 +368,7 @@ void SPlasticSourceControlLocksWidget::OnHiddenColumnsListChanged() void SPlasticSourceControlLocksWidget::OnSearchTextChanged(const FText& InFilterText) { SearchTextFilter->SetRawFilterText(InFilterText); - FileSearchBox->SetError(SearchTextFilter->GetFilterErrorText()); + LockSearchBox->SetError(SearchTextFilter->GetFilterErrorText()); } void SPlasticSourceControlLocksWidget::PopulateItemSearchStrings(const FPlasticSourceControlLock& InItem, TArray& OutStrings) @@ -727,8 +747,9 @@ void SPlasticSourceControlLocksWidget::Tick(const FGeometry& AllottedGeometry, c if (bShouldRefresh) { - RequestLocksRefresh(false); + RequestLocksRefresh(bShouldInvalidateLocksCache); bShouldRefresh = false; + bShouldInvalidateLocksCache = false; } if (bIsRefreshing) @@ -830,7 +851,8 @@ FReply SPlasticSourceControlLocksWidget::OnKeyDown(const FGeometry& MyGeometry, if (InKeyEvent.GetKey() == EKeys::F5) { // Pressing F5 refreshes the list of locks - RequestLocksRefresh(true); + bShouldRefresh = true; + bShouldInvalidateLocksCache = true; return FReply::Handled(); } else if ((InKeyEvent.GetKey() == EKeys::Delete) || (InKeyEvent.GetKey() == EKeys::BackSpace)) diff --git a/Source/PlasticSourceControl/Private/SPlasticSourceControlLocksWidget.h b/Source/PlasticSourceControl/Private/SPlasticSourceControlLocksWidget.h index 6ea4a5d7..82a6fd9a 100644 --- a/Source/PlasticSourceControl/Private/SPlasticSourceControlLocksWidget.h +++ b/Source/PlasticSourceControl/Private/SPlasticSourceControlLocksWidget.h @@ -77,7 +77,7 @@ class SPlasticSourceControlLocksWidget : public SCompoundWidget virtual FReply OnKeyDown(const FGeometry& MyGeometry, const FKeyEvent& InKeyEvent) override; private: - TSharedPtr FileSearchBox; + TSharedPtr LockSearchBox; FName PrimarySortedColumn; FName SecondarySortedColumn; @@ -87,6 +87,7 @@ class SPlasticSourceControlLocksWidget : public SCompoundWidget TArray HiddenColumnsList; bool bShouldRefresh = false; + bool bShouldInvalidateLocksCache = false; bool bSourceControlAvailable = false; FText RefreshStatus;