// Copyright Epic Games, Inc. All Rights Reserved. #include "SMergeTreeView.h" #include "Widgets/Layout/SSplitter.h" #include "SCSDiff.h" void SMergeTreeView::Construct(const FArguments InArgs , const FBlueprintMergeData& InData , FOnMergeNodeSelected SelectionCallback , TArray< TSharedPtr >& OutTreeEntries , TArray< TSharedPtr >& OutRealDifferences , TArray< TSharedPtr >& OutConflicts) { Data = InData; CurrentDifference = -1; CurrentMergeConflict = -1; // generate controls: // EMergeParticipant::Remote { SCSViews.Push( MakeShareable(new FSCSDiff(InData.BlueprintRemote)) ); } // EMergeParticipant::Base { SCSViews.Push( MakeShareable(new FSCSDiff(InData.BlueprintBase)) ); } // EMergeParticipant::Local { SCSViews.Push( MakeShareable(new FSCSDiff(InData.BlueprintLocal)) ); } TArray< FSCSResolvedIdentifier > RemoteHierarchy = GetRemoteView()->GetDisplayedHierarchy(); TArray< FSCSResolvedIdentifier > BaseHierarchy = GetBaseView()->GetDisplayedHierarchy(); TArray< FSCSResolvedIdentifier > LocalHierarchy = GetLocalView()->GetDisplayedHierarchy(); FSCSDiffRoot RemoteDifferingProperties; DiffUtils::CompareUnrelatedSCS(InData.BlueprintBase, BaseHierarchy, InData.BlueprintRemote, RemoteHierarchy, RemoteDifferingProperties ); FSCSDiffRoot LocalDifferingProperties; DiffUtils::CompareUnrelatedSCS(InData.BlueprintBase, BaseHierarchy, InData.BlueprintLocal, LocalHierarchy, LocalDifferingProperties); DifferingProperties = RemoteDifferingProperties; DifferingProperties.Entries.Append( LocalDifferingProperties.Entries ); struct FSCSDiffPair { const FSCSDiffEntry* Local; const FSCSDiffEntry* Remote; }; TArray < FSCSDiffPair > ConflictingDifferences; /* This predicate sorts the list of differing properties so that those that are 'earlier' in the tree appear first. For example, if we get the following two trees back: B added at position (3, 2, 1) C removed at position (1, 2) and D added at position (4, 2, 1) the resulting list will be [C, B, D]: */ const auto SortTreePredicate = []( const FSCSDiffEntry& A, const FSCSDiffEntry& B ) { int32 Idx = 0; const TArray& ATreeAddress = A.TreeIdentifier.TreeLocation; const TArray& BTreeAddress = B.TreeIdentifier.TreeLocation; while(true) { if( !ATreeAddress.IsValidIndex(Idx) ) { // A has a shorter address, show it first: return true; } else if( !BTreeAddress.IsValidIndex(Idx) ) { // B has a shorter address, show it first: return false; } else if( ATreeAddress[Idx] < BTreeAddress[Idx] ) { // A has a lower index, show it first: return true; } else if( ATreeAddress[Idx] > BTreeAddress[Idx] ) { // B has a lower index, show it first: return false; } else { // tie, go to the next level of the tree: ++Idx; } } }; RemoteDifferingProperties.Entries.Sort(SortTreePredicate); LocalDifferingProperties.Entries.Sort(SortTreePredicate); const FText RemoteLabel = NSLOCTEXT("SMergeTreeView", "RemoteLabel", "Remote"); const FText LocalLabel = NSLOCTEXT("SMergeTreeView", "LocalLabel", "Local"); struct FSCSMergeEntry { FText Label; FSCSIdentifier Identifier; FPropertySoftPath PropertyIdentifier; bool bConflicted; }; TArray Entries; bool bAnyConflict = false; int RemoteIter = 0; int LocalIter = 0; while( RemoteIter != RemoteDifferingProperties.Entries.Num() || LocalIter != LocalDifferingProperties.Entries.Num() ) { if (RemoteIter != RemoteDifferingProperties.Entries.Num() && LocalIter != LocalDifferingProperties.Entries.Num()) { // check for conflicts: const FSCSDiffEntry& Remote = RemoteDifferingProperties.Entries[RemoteIter]; const FSCSDiffEntry& Local = LocalDifferingProperties.Entries[LocalIter]; if( Remote.TreeIdentifier == Local.TreeIdentifier) { bool bConflicting = true; if( Remote.DiffType == ETreeDiffType::NODE_PROPERTY_CHANGED && Local.DiffType == ETreeDiffType::NODE_PROPERTY_CHANGED ) { // conflict only if property changed is the same: bConflicting = Remote.PropertyDiff.Identifier == Local.PropertyDiff.Identifier; } if( bConflicting ) { bAnyConflict = true; FSCSMergeEntry Entry = { FText::Format(NSLOCTEXT("SMergeTreeView", "ConflictIdentifier", "CONFLICT: {0} conflicts with {1}"), DiffViewUtils::SCSDiffMessage(Remote, RemoteLabel), DiffViewUtils::SCSDiffMessage(Local, LocalLabel)), Remote.TreeIdentifier, Remote.DiffType == ETreeDiffType::NODE_PROPERTY_CHANGED ? Remote.PropertyDiff.Identifier : Local.PropertyDiff.Identifier, true }; // create a tree entry that describes both the local and remote change.. Entries.Push( Entry ); ++RemoteIter; ++LocalIter; continue; } } } // no possibility of conflict, advance the entry that has a 'lower' tree identifier, keeping in mind that tree identifier // may be equal, and that in that case we need to use the property identifier as a tiebreaker: const FSCSDiffEntry* Remote = RemoteIter != RemoteDifferingProperties.Entries.Num() ? &RemoteDifferingProperties.Entries[RemoteIter] : nullptr; const FSCSDiffEntry* Local = LocalIter != LocalDifferingProperties.Entries.Num() ? &LocalDifferingProperties.Entries[LocalIter] : nullptr; if( Local && ( !Remote || SortTreePredicate( *Local, *Remote ) ) ) { FSCSMergeEntry Entry = { DiffViewUtils::SCSDiffMessage(*Local, LocalLabel), Local->TreeIdentifier, Local->PropertyDiff.Identifier, false }; Entries.Push( Entry ); ++LocalIter; } else { FSCSMergeEntry Entry = { DiffViewUtils::SCSDiffMessage(*Remote, RemoteLabel), Remote->TreeIdentifier, Remote->PropertyDiff.Identifier, false }; Entries.Push( Entry ); ++RemoteIter; } } const auto CreateSCSMergeWidget = [](FSCSMergeEntry Entry) -> TSharedRef { return SNew(STextBlock) .Text(Entry.Label) .ColorAndOpacity(Entry.bConflicted ? DiffViewUtils::Conflicting() : DiffViewUtils::Differs()); }; const auto FocusSCSDifferenceEntry = [](FSCSMergeEntry Entry, SMergeTreeView* Parent, FOnMergeNodeSelected InSelectionCallback) { InSelectionCallback.ExecuteIfBound(); Parent->HighlightDifference(Entry.Identifier, Entry.PropertyIdentifier); }; TArray< TSharedPtr > Children; for ( const auto& Difference : Entries ) { auto Entry = TSharedPtr( new FBlueprintDifferenceTreeEntry( FOnDiffEntryFocused::CreateStatic(FocusSCSDifferenceEntry, Difference, this, SelectionCallback) , FGenerateDiffEntryWidget::CreateStatic(CreateSCSMergeWidget, Difference) , TArray< TSharedPtr >() ) ); Children.Push(Entry); OutRealDifferences.Push(Entry); if( Difference.bConflicted ) { OutConflicts.Push(Entry); } } DifferingProperties.Entries.Sort(SortTreePredicate); const auto ForwardSelection = [](FOnMergeNodeSelected InSelectionCallback) { // This allows the owning control to focus the correct tab (or do whatever else it likes): InSelectionCallback.ExecuteIfBound(); }; const bool bHasDiffferences = Children.Num() != 0; if( !bHasDiffferences ) { Children.Push( FBlueprintDifferenceTreeEntry::NoDifferencesEntry() ); } TSharedPtr Category = FBlueprintDifferenceTreeEntry::CreateCategoryEntryForMerge( NSLOCTEXT("FBlueprintDifferenceTreeEntry", "SCSLabel", "Components"), NSLOCTEXT("FBlueprintDifferenceTreeEntry", "SCSTooltip", "The list of changes made in the Components panel"), FOnDiffEntryFocused::CreateStatic(ForwardSelection, SelectionCallback), Children, RemoteDifferingProperties.Entries.Num() != 0, LocalDifferingProperties.Entries.Num() != 0, bAnyConflict); OutTreeEntries.Push(Category); ChildSlot[ SNew(SSplitter) + SSplitter::Slot() [ GetRemoteView()->TreeWidget() ] + SSplitter::Slot() [ GetBaseView()->TreeWidget() ] + SSplitter::Slot() [ GetLocalView()->TreeWidget() ] ]; } void SMergeTreeView::HighlightDifference(FSCSIdentifier TreeIdentifier, FPropertySoftPath Property) { for (auto& View : SCSViews) { View->HighlightProperty(TreeIdentifier.Name, Property); } } TSharedRef& SMergeTreeView::GetRemoteView() { return SCSViews[EMergeParticipant::Remote]; } TSharedRef& SMergeTreeView::GetBaseView() { return SCSViews[EMergeParticipant::Base]; } TSharedRef& SMergeTreeView::GetLocalView() { return SCSViews[EMergeParticipant::Local]; }