Files
BookRPG/Library/PackageCache/com.unity.collab-proxy@0b1559bcd34e/Editor/Views/Merge/Developer/MergeTreeView.cs

757 lines
23 KiB
C#
Raw Normal View History

2026-07-16 21:49:59 +01:00
using System;
using System.Collections.Generic;
using System.IO;
using UnityEditor.IMGUI.Controls;
using UnityEngine;
using Codice.Client.BaseCommands.Merge;
using Codice.Client.Common;
using Codice.CM.Common;
using PlasticGui;
using PlasticGui.WorkspaceWindow.Merge;
using Unity.PlasticSCM.Editor.UI;
using Unity.PlasticSCM.Editor.UI.Avatar;
using Unity.PlasticSCM.Editor.UI.Tree;
using UnityEditor;
#if UNITY_6000_2_OR_NEWER
using TreeViewItem = UnityEditor.IMGUI.Controls.TreeViewItem<int>;
#endif
namespace Unity.PlasticSCM.Editor.Views.Merge.Developer
{
internal class MergeTreeView : PlasticTreeView
{
internal GenericMenu Menu { get { return mMenu.Menu; } }
internal MergeTreeView(
WorkspaceInfo wkInfo,
MergeTreeHeaderState headerState,
List<string> columnNames,
MergeViewMenu menu,
bool enableFiltering,
Action onCheckedNodeChanged,
Action updateEmptyStateMessageAction)
{
mWkInfo = wkInfo;
mColumnNames = columnNames;
mMenu = menu;
mEnableFiltering = enableFiltering;
mOnCheckedNodeChanged = onCheckedNodeChanged;
mUpdateEmptyStateMessageAction = updateEmptyStateMessageAction;
multiColumnHeader = new MultiColumnHeader(headerState);
multiColumnHeader.canSort = true;
multiColumnHeader.sortingChanged += SortingChanged;
customFoldoutYOffset = UnityConstants.TREEVIEW_FOLDOUT_Y_OFFSET;
mDelayedFilterAction = new DelayedActionBySecondsRunner(
DelayedSearchChanged, UnityConstants.SEARCH_DELAYED_INPUT_ACTION_INTERVAL);
}
protected override bool CanChangeExpandedState(TreeViewItem item)
{
return item is ChangeCategoryTreeViewItem;
}
protected override IList<TreeViewItem> BuildRows(TreeViewItem rootItem)
{
try
{
RegenerateRows(
mMergeTree,
mTreeViewItemIds,
this,
rootItem,
mRows,
mExpandCategories);
}
finally
{
mExpandCategories = false;
}
return mRows;
}
protected override void CommandEventHandling()
{
// NOTE - empty override to prevent crash when pressing ctrl-a in the treeview
}
protected override void SearchChanged(string newSearch)
{
mDelayedFilterAction.Run();
}
protected override void ContextClickedItem(int id)
{
mMenu.Popup();
Repaint();
}
public override void OnGUI(Rect rect)
{
base.OnGUI(rect);
if (!HasKeyboardFocus())
return;
Event e = Event.current;
if (e.type != EventType.KeyDown)
return;
bool isProcessed = mMenu.ProcessKeyActionIfNeeded(e);
if (isProcessed)
e.Use();
}
protected override void RowGUI(RowGUIArgs args)
{
if (args.item is ChangeCategoryTreeViewItem)
{
ChangeCategoryTreeViewItem categoryItem =
(ChangeCategoryTreeViewItem)args.item;
CategoryTreeViewItemGUI(
args.rowRect,
categoryItem,
mOnCheckedNodeChanged,
GetSolvedChildrenCount(categoryItem.Category, mSolvedFileConflicts),
args.selected,
args.focused,
mEnableFiltering);
return;
}
if (args.item is ChangeTreeViewItem)
{
ChangeTreeViewItem changeTreeViewItem =
(ChangeTreeViewItem)args.item;
MergeChangeInfo changeInfo =
changeTreeViewItem.ChangeInfo;
bool isCurrentConflict = IsCurrent.Conflict(
changeInfo,
mMergeTree.GetMetaChange(changeInfo),
mSolvedFileConflicts);
bool isSolvedConflict = IsSolved.Conflict(
changeInfo,
mMergeTree.GetMetaChange(changeInfo),
mSolvedFileConflicts);
MergeTreeViewItemGUI(
mWkInfo.ClientPath,
mMergeTree,
this,
changeTreeViewItem,
mOnCheckedNodeChanged,
args,
isCurrentConflict,
isSolvedConflict,
mEnableFiltering,
Repaint);
return;
}
base.RowGUI(args);
}
internal void SelectFirstUnsolvedDirectoryConflict()
{
foreach (MergeChangesCategory category in mMergeTree.GetNodes())
{
if (category.CategoryType != MergeChangesCategory.Type.DirectoryConflicts)
continue;
foreach (MergeChangeInfo changeInfo in category.GetChanges())
{
if (changeInfo.DirectoryConflict.IsResolved())
continue;
if (mEnableFiltering && !(((ICheckablePlasticTreeNode)changeInfo).IsChecked() ?? false))
continue;
int itemId = -1;
if (mTreeViewItemIds.TryGetInfoItemId(changeInfo, out itemId))
{
SetSelection(new List<int>() { itemId });
return;
}
}
}
}
internal void BuildModel(UnityMergeTree tree)
{
mTreeViewItemIds.Clear();
mMergeTree = tree;
mSolvedFileConflicts = null;
mExpandCategories = true;
}
internal void Refilter()
{
Filter filter = new Filter(searchString);
mMergeTree.Filter(filter, mColumnNames);
mExpandCategories = true;
mUpdateEmptyStateMessageAction();
}
internal void Sort()
{
if (mMergeTree == null)
return;
int sortedColumnIdx = multiColumnHeader.state.sortedColumnIndex;
bool sortAscending = multiColumnHeader.IsSortedAscending(sortedColumnIdx);
mMergeTree.Sort(mColumnNames[sortedColumnIdx], sortAscending);
}
internal void UpdateSolvedFileConflicts(
MergeSolvedFileConflicts solvedFileConflicts)
{
mSolvedFileConflicts = solvedFileConflicts;
}
internal MergeChangeInfo GetMetaChange(MergeChangeInfo change)
{
if (change == null)
return null;
return mMergeTree.GetMetaChange(change);
}
internal void FillWithMeta(List<MergeChangeInfo> changes)
{
mMergeTree.FillWithMeta(changes);
}
internal bool SelectionHasMeta()
{
MergeChangeInfo selectedChangeInfo = GetSelectedMergeChange();
if (selectedChangeInfo == null)
return false;
return mMergeTree.HasMeta(selectedChangeInfo);
}
internal MergeChangeInfo GetSelectedMergeChange()
{
IList<int> selectedIds = GetSelection();
if (selectedIds.Count != 1)
return null;
int selectedId = selectedIds[0];
foreach (KeyValuePair<MergeChangeInfo, int> item
in mTreeViewItemIds.GetInfoItems())
{
if (selectedId == item.Value)
return item.Key;
}
return null;
}
internal List<MergeChangeInfo> GetSelectedMergeChanges()
{
List<MergeChangeInfo> result = new List<MergeChangeInfo>();
IList<int> selectedIds = GetSelection();
if (selectedIds.Count == 0)
return result;
foreach (KeyValuePair<MergeChangeInfo, int> item
in mTreeViewItemIds.GetInfoItems())
{
if (!selectedIds.Contains(item.Value))
continue;
result.Add(item.Key);
}
return result;
}
internal List<MergeChangeInfo> GetSelectedFileConflicts()
{
List<MergeChangeInfo> result = new List<MergeChangeInfo>();
IList<int> selectedIds = GetSelection();
if (selectedIds.Count == 0)
return result;
foreach (KeyValuePair<MergeChangeInfo, int> item
in mTreeViewItemIds.GetInfoItems())
{
if (!selectedIds.Contains(item.Value))
continue;
if (item.Key.CategoryType !=
MergeChangesCategory.Type.FileConflicts)
continue;
result.Add(item.Key);
}
return result;
}
void DelayedSearchChanged()
{
Refilter();
Sort();
Reload();
TableViewOperations.ScrollToSelection(this);
}
void SortingChanged(MultiColumnHeader multiColumnHeader)
{
Sort();
Reload();
}
internal int GetTotalItemCount()
{
if (mMergeTree == null)
return 0;
List<MergeChangesCategory> categories = mMergeTree.GetNodes();
if (categories == null)
return 0;
int totalCount = 0;
foreach (MergeChangesCategory category in categories)
{
totalCount += category.GetChildrenCount();
}
return totalCount;
}
static void RegenerateRows(
UnityMergeTree mergeTree,
TreeViewItemIds<MergeChangesCategory, MergeChangeInfo> treeViewItemIds,
MergeTreeView treeView,
TreeViewItem rootItem,
List<TreeViewItem> rows,
bool expandCategories)
{
if (mergeTree == null)
return;
ClearRows(rootItem, rows);
List<MergeChangesCategory> categories = mergeTree.GetNodes();
if (categories == null)
return;
List<int> categoriesToExpand = new List<int>();
foreach (MergeChangesCategory category in categories)
{
int categoryId;
if (!treeViewItemIds.TryGetCategoryItemId(category, out categoryId))
categoryId = treeViewItemIds.AddCategoryItem(category);
ChangeCategoryTreeViewItem categoryTreeViewItem =
new ChangeCategoryTreeViewItem(categoryId, category);
rootItem.AddChild(categoryTreeViewItem);
rows.Add(categoryTreeViewItem);
if (!ShouldExpandCategory(
treeView,
categoryTreeViewItem,
expandCategories,
categories.Count))
continue;
categoriesToExpand.Add(categoryTreeViewItem.id);
foreach (MergeChangeInfo changeInfo in category.GetChanges())
{
int differenceId;
if (!treeViewItemIds.TryGetInfoItemId(changeInfo, out differenceId))
differenceId = treeViewItemIds.AddInfoItem(changeInfo);
TreeViewItem changeTreeViewItem =
new ChangeTreeViewItem(differenceId, changeInfo);
categoryTreeViewItem.AddChild(changeTreeViewItem);
rows.Add(changeTreeViewItem);
}
}
treeView.state.expandedIDs = categoriesToExpand;
}
static void ClearRows(
TreeViewItem rootItem,
List<TreeViewItem> rows)
{
if (rootItem.hasChildren)
rootItem.children.Clear();
rows.Clear();
}
static void UpdateCheckStateForSelection(
MergeTreeView treeView,
ChangeTreeViewItem senderTreeViewItem)
{
IList<int> selectedIds = treeView.GetSelection();
if (selectedIds.Count <= 1)
return;
if (!selectedIds.Contains(senderTreeViewItem.id))
return;
bool isChecked = ((ICheckablePlasticTreeNode)senderTreeViewItem.ChangeInfo).IsChecked() ?? false;
foreach (TreeViewItem treeViewItem in treeView.FindRows(selectedIds))
{
if (treeViewItem is ChangeCategoryTreeViewItem)
{
((ICheckablePlasticTreeCategory)((ChangeCategoryTreeViewItem)treeViewItem)
.Category).UpdateCheckedState(isChecked);
continue;
}
((ICheckablePlasticTreeNode)((ChangeTreeViewItem)treeViewItem)
.ChangeInfo).UpdateCheckedState(isChecked);
}
}
static void CategoryTreeViewItemGUI(
Rect rowRect,
ChangeCategoryTreeViewItem item,
Action onCheckedNodeChanged,
int solvedChildrenCount,
bool isSelected,
bool isFocused,
bool enableFiltering)
{
string label = item.Category.GetCategoryName();
string infoLabel = item.Category.GetChildrenCountText();
DefaultStyles.label = GetCategoryStyle(
item.Category,
solvedChildrenCount,
isSelected);
if (!enableFiltering)
{
DrawTreeViewItem.ForIndentedItem(
rowRect,
item.depth,
label,
infoLabel,
isSelected,
isFocused);
DefaultStyles.label = UnityStyles.Tree.Label;
return;
}
bool wasChecked = CheckableItems.GetIsCheckedValueForCategory(item.Category) ?? false;
bool hadCheckedChildren = ((ICheckablePlasticTreeCategory)item.Category).GetCheckedChangesCount() > 0;
bool isChecked = DrawTreeViewItem.ForCheckableIndentedItem(
rowRect,
item.depth,
label,
infoLabel,
isSelected,
isFocused,
wasChecked,
hadCheckedChildren,
hadPartiallyCheckedChildren: false);
DefaultStyles.label = UnityStyles.Tree.Label;
if (!wasChecked && isChecked)
{
((ICheckablePlasticTreeCategory)item.Category).UpdateCheckedState(true);
onCheckedNodeChanged();
return;
}
if (wasChecked && !isChecked)
{
((ICheckablePlasticTreeCategory)item.Category).UpdateCheckedState(false);
onCheckedNodeChanged();
return;
}
}
static void MergeTreeViewItemGUI(
string wkPath,
UnityMergeTree mergeTree,
MergeTreeView treeView,
ChangeTreeViewItem item,
Action onCheckedNodeChanged,
RowGUIArgs args,
bool isCurrentConflict,
bool isSolvedConflict,
bool enableFiltering,
Action avatarLoadedAction)
{
for (int visibleColumnIdx = 0; visibleColumnIdx < args.GetNumVisibleColumns(); visibleColumnIdx++)
{
Rect cellRect = args.GetCellRect(visibleColumnIdx);
MergeTreeColumn column =
(MergeTreeColumn)args.GetColumn(visibleColumnIdx);
MergeTreeViewItemCellGUI(
wkPath,
cellRect,
treeView.rowHeight,
mergeTree,
treeView,
item,
onCheckedNodeChanged,
column,
avatarLoadedAction,
args.selected,
args.focused,
isCurrentConflict,
isSolvedConflict,
enableFiltering);
}
}
static void MergeTreeViewItemCellGUI(
string wkPath,
Rect rect,
float rowHeight,
UnityMergeTree mergeTree,
MergeTreeView treeView,
ChangeTreeViewItem item,
Action onCheckedNodeChanged,
MergeTreeColumn column,
Action avatarLoadedAction,
bool isSelected,
bool isFocused,
bool isCurrentConflict,
bool isSolvedConflict,
bool enableFiltering)
{
MergeChangeInfo mergeChange = item.ChangeInfo;
string label = mergeChange.GetColumnText(
MergeTreeHeaderState.GetColumnName(column));
if (column == MergeTreeColumn.Path)
{
if (mergeTree.HasMeta(item.ChangeInfo))
label = string.Concat(label, UnityConstants.TREEVIEW_META_LABEL);
Texture icon = GetIcon(wkPath, mergeChange);
Texture overlayIcon =
GetChangesOverlayIcon.ForPlasticMergeChange(
mergeChange, isSolvedConflict);
if (!enableFiltering)
{
DrawTreeViewItem.ForItemCell(
rect,
rowHeight,
item.depth,
icon,
null,
overlayIcon,
label,
isSelected,
isFocused,
isCurrentConflict,
false,
DrawTreeViewItem.TextTrimming.Path);
return;
}
bool wasChecked = ((ICheckablePlasticTreeNode)mergeChange).IsChecked() ?? false;
bool isChecked = DrawTreeViewItem.ForCheckableItemCell(
rect, rowHeight, item.depth,
icon, null, overlayIcon, label,
isSelected, isFocused, isCurrentConflict,
wasChecked,
DrawTreeViewItem.TextTrimming.Path);
((ICheckablePlasticTreeNode)mergeChange).UpdateCheckedState(isChecked);
if (wasChecked != isChecked)
{
UpdateCheckStateForSelection(treeView, item);
onCheckedNodeChanged();
}
return;
}
if (column == MergeTreeColumn.Author)
{
DrawTreeViewItem.ForItemCell(
rect,
rowHeight,
-1,
GetAvatar.ForEmail(label, avatarLoadedAction),
null,
null,
label,
isSelected,
isFocused,
isCurrentConflict,
false);
return;
}
if (column == MergeTreeColumn.Size)
{
// If there is a meta file, add the meta file to the file size so that it is consistent
// with the Merge overview
if (mergeTree.HasMeta(item.ChangeInfo))
{
MergeChangeInfo metaFileInfo = mergeTree.GetMetaChange(mergeChange);
long metaFileSize = metaFileInfo.GetSize();
long fileSize = mergeChange.GetSize();
label = SizeConverter.ConvertToSizeString(fileSize + metaFileSize);
}
DrawTreeViewItem.ForSecondaryLabelRightAligned(
rect, label, isSelected, isFocused, isCurrentConflict);
return;
}
DrawTreeViewItem.ForSecondaryLabel(
rect, label, isSelected, isFocused, isCurrentConflict);
}
static Texture GetIcon(
string wkPath,
MergeChangeInfo mergeChange)
{
RevisionInfo revInfo = mergeChange.GetRevision();
bool isDirectory = revInfo.
Type == EnumRevisionType.enDirectory;
if (isDirectory || mergeChange.IsXLink())
return Images.GetFolderIcon();
string fullPath = WorkspacePath.GetWorkspacePathFromCmPath(
wkPath,
mergeChange.GetPath(),
Path.DirectorySeparatorChar);
return Images.GetFileIcon(fullPath);
}
static GUIStyle GetCategoryStyle(
MergeChangesCategory category,
int solvedChildrenCount,
bool isSelected)
{
if (isSelected)
return UnityStyles.Tree.Label;
if (category.CategoryType == MergeChangesCategory.Type.FileConflicts ||
category.CategoryType == MergeChangesCategory.Type.DirectoryConflicts)
{
return category.GetChildrenCount() > solvedChildrenCount ?
UnityStyles.Tree.RedLabel : UnityStyles.Tree.GreenLabel;
}
return UnityStyles.Tree.Label;
}
static bool ShouldExpandCategory(
MergeTreeView treeView,
ChangeCategoryTreeViewItem categoryTreeViewItem,
bool expandCategories,
int categoriesCount)
{
if (expandCategories)
{
if (categoriesCount == 1)
return true;
if (categoryTreeViewItem.Category.CategoryType ==
MergeChangesCategory.Type.FileConflicts)
return true;
if (categoryTreeViewItem.Category.GetChildrenCount() >
NODES_TO_EXPAND_CATEGORY)
return false;
return true;
}
return treeView.IsExpanded(categoryTreeViewItem.id);
}
static int GetSolvedChildrenCount(
MergeChangesCategory category,
MergeSolvedFileConflicts solvedFileConflicts)
{
int solvedDirConflicts = 0;
if (category.CategoryType == MergeChangesCategory.Type.DirectoryConflicts)
{
foreach (MergeChangeInfo change in category.GetChanges())
{
if (change.DirectoryConflict.IsResolved())
solvedDirConflicts++;
}
return solvedDirConflicts;
}
return (solvedFileConflicts == null) ? 0 :
solvedFileConflicts.GetCount();
}
bool mExpandCategories;
TreeViewItemIds<MergeChangesCategory, MergeChangeInfo> mTreeViewItemIds =
new TreeViewItemIds<MergeChangesCategory, MergeChangeInfo>();
MergeSolvedFileConflicts mSolvedFileConflicts;
UnityMergeTree mMergeTree;
DelayedActionBySecondsRunner mDelayedFilterAction;
readonly WorkspaceInfo mWkInfo;
readonly List<string> mColumnNames;
readonly MergeViewMenu mMenu;
readonly bool mEnableFiltering;
readonly Action mOnCheckedNodeChanged;
readonly Action mUpdateEmptyStateMessageAction;
const int NODES_TO_EXPAND_CATEGORY = 10;
}
}