Files
UnrealEngine/Engine/Source/Developer/Merge/Private/SMergeTreeView.cpp
2025-05-18 13:04:45 +08:00

293 lines
8.5 KiB
C++

// 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<FBlueprintDifferenceTreeEntry> >& OutTreeEntries
, TArray< TSharedPtr<FBlueprintDifferenceTreeEntry> >& OutRealDifferences
, TArray< TSharedPtr<FBlueprintDifferenceTreeEntry> >& 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<int32>& ATreeAddress = A.TreeIdentifier.TreeLocation;
const TArray<int32>& 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<FSCSMergeEntry> 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<SWidget>
{
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<FBlueprintDifferenceTreeEntry> > Children;
for ( const auto& Difference : Entries )
{
auto Entry = TSharedPtr<FBlueprintDifferenceTreeEntry>(
new FBlueprintDifferenceTreeEntry(
FOnDiffEntryFocused::CreateStatic(FocusSCSDifferenceEntry, Difference, this, SelectionCallback)
, FGenerateDiffEntryWidget::CreateStatic(CreateSCSMergeWidget, Difference)
, TArray< TSharedPtr<FBlueprintDifferenceTreeEntry> >()
)
);
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<FBlueprintDifferenceTreeEntry> 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<FSCSDiff>& SMergeTreeView::GetRemoteView()
{
return SCSViews[EMergeParticipant::Remote];
}
TSharedRef<FSCSDiff>& SMergeTreeView::GetBaseView()
{
return SCSViews[EMergeParticipant::Base];
}
TSharedRef<FSCSDiff>& SMergeTreeView::GetLocalView()
{
return SCSViews[EMergeParticipant::Local];
}