IncomingChangesTreeView.cs 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595
  1. using System;
  2. using System.IO;
  3. using System.Collections.Generic;
  4. using UnityEditor.IMGUI.Controls;
  5. using UnityEngine;
  6. using Codice.CM.Common;
  7. using Codice.Client.Common;
  8. using PlasticGui.Gluon.WorkspaceWindow.Views.IncomingChanges;
  9. using Unity.PlasticSCM.Editor.UI;
  10. using Unity.PlasticSCM.Editor.UI.Tree;
  11. namespace Unity.PlasticSCM.Editor.Views.IncomingChanges.Gluon
  12. {
  13. internal class IncomingChangesTreeView : TreeView
  14. {
  15. internal IncomingChangesTreeView(
  16. WorkspaceInfo wkInfo,
  17. IncomingChangesTreeHeaderState headerState,
  18. List<string> columnNames,
  19. IncomingChangesViewMenu menu,
  20. Action onCheckedNodeChanged)
  21. : base(new TreeViewState())
  22. {
  23. mWkInfo = wkInfo;
  24. mColumnNames = columnNames;
  25. mMenu = menu;
  26. mOnCheckedNodeChanged = onCheckedNodeChanged;
  27. multiColumnHeader = new MultiColumnHeader(headerState);
  28. multiColumnHeader.canSort = true;
  29. multiColumnHeader.sortingChanged += SortingChanged;
  30. customFoldoutYOffset = UnityConstants.TREEVIEW_FOLDOUT_Y_OFFSET;
  31. rowHeight = UnityConstants.TREEVIEW_ROW_HEIGHT;
  32. showAlternatingRowBackgrounds = true;
  33. }
  34. public override IList<TreeViewItem> GetRows()
  35. {
  36. return mRows;
  37. }
  38. protected override bool CanChangeExpandedState(TreeViewItem item)
  39. {
  40. return item is ChangeCategoryTreeViewItem;
  41. }
  42. protected override TreeViewItem BuildRoot()
  43. {
  44. return new TreeViewItem(0, -1, string.Empty);
  45. }
  46. protected override IList<TreeViewItem> BuildRows(TreeViewItem rootItem)
  47. {
  48. try
  49. {
  50. RegenerateRows(
  51. mIncomingChangesTree, mTreeViewItemIds, this,
  52. rootItem, mRows, mExpandCategories);
  53. }
  54. finally
  55. {
  56. mExpandCategories = false;
  57. }
  58. return mRows;
  59. }
  60. protected override void CommandEventHandling()
  61. {
  62. // NOTE - empty override to prevent crash when pressing ctrl-a in the treeview
  63. }
  64. protected override void ContextClickedItem(int id)
  65. {
  66. mMenu.Popup();
  67. Repaint();
  68. }
  69. protected override void RowGUI(RowGUIArgs args)
  70. {
  71. DrawTreeViewItem.InitializeStyles();
  72. if (args.item is ChangeCategoryTreeViewItem)
  73. {
  74. CategoryTreeViewItemGUI(
  75. args.rowRect, rowHeight,
  76. (ChangeCategoryTreeViewItem)args.item,
  77. mOnCheckedNodeChanged,
  78. mSolvedConflicts.Count,
  79. args.selected, args.focused);
  80. return;
  81. }
  82. if (args.item is ChangeTreeViewItem)
  83. {
  84. ChangeTreeViewItem changeTreeViewItem =
  85. (ChangeTreeViewItem)args.item;
  86. IncomingChangeInfo changeInfo = changeTreeViewItem.ChangeInfo;
  87. IncomingChangeInfo metaChangeInfo = mIncomingChangesTree.GetMetaChange(
  88. changeInfo);
  89. bool isCurrentConflict = IsCurrentConflict(
  90. changeInfo,
  91. metaChangeInfo,
  92. mCurrentConflict);
  93. bool isSolvedConflict = IsSolvedConflict(
  94. changeInfo,
  95. metaChangeInfo,
  96. mSolvedConflicts);
  97. IncomingChangeTreeViewItemGUI(
  98. mWkInfo.ClientPath,
  99. mIncomingChangesTree,
  100. this,
  101. changeTreeViewItem,
  102. mOnCheckedNodeChanged, args,
  103. isCurrentConflict,
  104. isSolvedConflict);
  105. return;
  106. }
  107. base.RowGUI(args);
  108. }
  109. internal void BuildModel(UnityIncomingChangesTree tree)
  110. {
  111. mTreeViewItemIds.Clear();
  112. mIncomingChangesTree = tree;
  113. mSolvedConflicts = new List<IncomingChangeInfo>();
  114. mCurrentConflict = null;
  115. mExpandCategories = true;
  116. }
  117. internal void UpdateSolvedFileConflicts(
  118. List<IncomingChangeInfo> solvedConflicts,
  119. IncomingChangeInfo currentConflict)
  120. {
  121. mSolvedConflicts = solvedConflicts;
  122. mCurrentConflict = currentConflict;
  123. }
  124. internal void Sort()
  125. {
  126. int sortedColumnIdx = multiColumnHeader.state.sortedColumnIndex;
  127. bool sortAscending = multiColumnHeader.IsSortedAscending(sortedColumnIdx);
  128. mIncomingChangesTree.Sort(
  129. mColumnNames[sortedColumnIdx],
  130. sortAscending);
  131. }
  132. internal IncomingChangeInfo GetMetaChange(IncomingChangeInfo change)
  133. {
  134. if (change == null)
  135. return null;
  136. return mIncomingChangesTree.GetMetaChange(change);
  137. }
  138. internal void FillWithMeta(List<IncomingChangeInfo> changes)
  139. {
  140. mIncomingChangesTree.FillWithMeta(changes);
  141. }
  142. internal bool SelectionHasMeta()
  143. {
  144. IncomingChangeInfo selectedChangeInfo = GetSelectedIncomingChange();
  145. if (selectedChangeInfo == null)
  146. return false;
  147. return mIncomingChangesTree.HasMeta(selectedChangeInfo);
  148. }
  149. internal IncomingChangeInfo GetSelectedIncomingChange()
  150. {
  151. IList<int> selectedIds = GetSelection();
  152. if (selectedIds.Count != 1)
  153. return null;
  154. int selectedId = selectedIds[0];
  155. foreach (KeyValuePair<IncomingChangeInfo, int> item
  156. in mTreeViewItemIds.GetInfoItems())
  157. {
  158. if (selectedId == item.Value)
  159. return item.Key;
  160. }
  161. return null;
  162. }
  163. internal List<IncomingChangeInfo> GetSelectedIncomingChanges()
  164. {
  165. List<IncomingChangeInfo> result = new List<IncomingChangeInfo>();
  166. IList<int> selectedIds = GetSelection();
  167. if (selectedIds.Count == 0)
  168. return result;
  169. foreach (KeyValuePair<IncomingChangeInfo, int> item
  170. in mTreeViewItemIds.GetInfoItems())
  171. {
  172. if (!selectedIds.Contains(item.Value))
  173. continue;
  174. result.Add(item.Key);
  175. }
  176. return result;
  177. }
  178. internal List<IncomingChangeInfo> GetSelectedFileConflicts()
  179. {
  180. List<IncomingChangeInfo> result = new List<IncomingChangeInfo>();
  181. IList<int> selectedIds = GetSelection();
  182. if (selectedIds.Count == 0)
  183. return result;
  184. foreach (KeyValuePair<IncomingChangeInfo, int> item
  185. in mTreeViewItemIds.GetInfoItems())
  186. {
  187. if (!selectedIds.Contains(item.Value))
  188. continue;
  189. if (item.Key.CategoryType != IncomingChangeCategory.Type.Conflicted)
  190. continue;
  191. result.Add(item.Key);
  192. }
  193. return result;
  194. }
  195. void SortingChanged(MultiColumnHeader multiColumnHeader)
  196. {
  197. Sort();
  198. Reload();
  199. }
  200. static bool IsCurrentConflict(
  201. IncomingChangeInfo changeInfo,
  202. IncomingChangeInfo metaChangeInfo,
  203. IncomingChangeInfo currentConflict)
  204. {
  205. if (metaChangeInfo == null)
  206. return currentConflict == changeInfo;
  207. return currentConflict == changeInfo ||
  208. currentConflict == metaChangeInfo;
  209. }
  210. static bool IsSolvedConflict(
  211. IncomingChangeInfo changeInfo,
  212. IncomingChangeInfo metaChangeInfo,
  213. List<IncomingChangeInfo> solvedConflicts)
  214. {
  215. if (metaChangeInfo == null)
  216. return solvedConflicts.Contains(changeInfo);
  217. return solvedConflicts.Contains(changeInfo) &&
  218. solvedConflicts.Contains(metaChangeInfo);
  219. }
  220. static void RegenerateRows(
  221. UnityIncomingChangesTree incomingChangesTree,
  222. TreeViewItemIds<IncomingChangeCategory, IncomingChangeInfo> treeViewItemIds,
  223. IncomingChangesTreeView treeView,
  224. TreeViewItem rootItem,
  225. List<TreeViewItem> rows,
  226. bool expandCategories)
  227. {
  228. if (incomingChangesTree == null)
  229. return;
  230. ClearRows(rootItem, rows);
  231. List<IncomingChangeCategory> categories = incomingChangesTree.GetNodes();
  232. if (categories == null)
  233. return;
  234. List<int> categoriesToExpand = new List<int>();
  235. foreach (IncomingChangeCategory category in categories)
  236. {
  237. int categoryId;
  238. if (!treeViewItemIds.TryGetCategoryItemId(category, out categoryId))
  239. categoryId = treeViewItemIds.AddCategoryItem(category);
  240. ChangeCategoryTreeViewItem categoryTreeViewItem =
  241. new ChangeCategoryTreeViewItem(categoryId, category);
  242. rootItem.AddChild(categoryTreeViewItem);
  243. rows.Add(categoryTreeViewItem);
  244. if (!ShouldExpandCategory(
  245. treeView, categoryTreeViewItem,
  246. categories.Count, expandCategories))
  247. continue;
  248. categoriesToExpand.Add(categoryTreeViewItem.id);
  249. foreach (IncomingChangeInfo incomingChange in category.GetChanges())
  250. {
  251. int changeId;
  252. if (!treeViewItemIds.TryGetInfoItemId(incomingChange, out changeId))
  253. changeId = treeViewItemIds.AddInfoItem(incomingChange);
  254. TreeViewItem changeTreeViewItem =
  255. new ChangeTreeViewItem(changeId, incomingChange);
  256. categoryTreeViewItem.AddChild(changeTreeViewItem);
  257. rows.Add(changeTreeViewItem);
  258. }
  259. }
  260. treeView.state.expandedIDs = categoriesToExpand;
  261. }
  262. static void ClearRows(
  263. TreeViewItem rootItem,
  264. List<TreeViewItem> rows)
  265. {
  266. if (rootItem.hasChildren)
  267. rootItem.children.Clear();
  268. rows.Clear();
  269. }
  270. static void UpdateCheckStateForSelection(
  271. IncomingChangesTreeView treeView,
  272. ChangeTreeViewItem senderTreeViewItem)
  273. {
  274. IList<int> selectedIds = treeView.GetSelection();
  275. if (selectedIds.Count <= 1)
  276. return;
  277. if (!selectedIds.Contains(senderTreeViewItem.id))
  278. return;
  279. bool isChecked = senderTreeViewItem.ChangeInfo.IsChecked();
  280. foreach (TreeViewItem treeViewItem in treeView.FindRows(selectedIds))
  281. {
  282. if (treeViewItem is ChangeCategoryTreeViewItem)
  283. {
  284. ((ChangeCategoryTreeViewItem)treeViewItem).Category
  285. .UpdateCheckedState(isChecked);
  286. continue;
  287. }
  288. ((ChangeTreeViewItem)treeViewItem).ChangeInfo
  289. .UpdateCheckedState(isChecked);
  290. }
  291. }
  292. static void CategoryTreeViewItemGUI(
  293. Rect rowRect,
  294. float rowHeight,
  295. ChangeCategoryTreeViewItem item,
  296. Action onCheckedNodeChanged,
  297. int solvedConflictsCount,
  298. bool isSelected,
  299. bool isFocused)
  300. {
  301. Texture icon = GetCategoryIcon(item.Category.CategoryType);
  302. string label = item.Category.GetHeaderText();
  303. bool wasChecked = item.Category.IsChecked();
  304. bool hadCheckedChildren = item.Category.GetCheckedChangesCount() > 0;
  305. DefaultStyles.label = GetCategoryStyle(
  306. item.Category, solvedConflictsCount, isSelected);
  307. bool isChecked = DrawTreeViewItem.ForCheckableCategoryItem(
  308. rowRect, rowHeight, item.depth, icon, label,
  309. isSelected, isFocused, wasChecked, hadCheckedChildren);
  310. DefaultStyles.label = UnityStyles.Tree.Label;
  311. if (!wasChecked && isChecked)
  312. {
  313. item.Category.UpdateCheckedState(true);
  314. onCheckedNodeChanged();
  315. return;
  316. }
  317. if (wasChecked && !isChecked)
  318. {
  319. item.Category.UpdateCheckedState(false);
  320. onCheckedNodeChanged();
  321. return;
  322. }
  323. }
  324. static void IncomingChangeTreeViewItemGUI(
  325. string wkPath,
  326. UnityIncomingChangesTree incomingChangesTree,
  327. IncomingChangesTreeView treeView,
  328. ChangeTreeViewItem item,
  329. Action onCheckedNodeChanged,
  330. RowGUIArgs args,
  331. bool isCurrentConflict,
  332. bool isSolvedConflict)
  333. {
  334. for (int visibleColumnIdx = 0; visibleColumnIdx < args.GetNumVisibleColumns(); visibleColumnIdx++)
  335. {
  336. Rect cellRect = args.GetCellRect(visibleColumnIdx);
  337. IncomingChangesTreeColumn column =
  338. (IncomingChangesTreeColumn)args.GetColumn(visibleColumnIdx);
  339. IncomingChangeTreeViewItemCellGUI(
  340. wkPath,
  341. cellRect,
  342. treeView.rowHeight,
  343. incomingChangesTree,
  344. treeView,
  345. item,
  346. onCheckedNodeChanged,
  347. column,
  348. args.selected,
  349. args.focused,
  350. isCurrentConflict,
  351. isSolvedConflict);
  352. }
  353. }
  354. static void IncomingChangeTreeViewItemCellGUI(
  355. string wkPath,
  356. Rect rect,
  357. float rowHeight,
  358. UnityIncomingChangesTree incomingChangesTree,
  359. IncomingChangesTreeView treeView,
  360. ChangeTreeViewItem item,
  361. Action onCheckedNodeChanged,
  362. IncomingChangesTreeColumn column,
  363. bool isSelected,
  364. bool isFocused,
  365. bool isCurrentConflict,
  366. bool isSolvedConflict)
  367. {
  368. IncomingChangeInfo incomingChange = item.ChangeInfo;
  369. string label = incomingChange.GetColumnText(
  370. IncomingChangesTreeHeaderState.GetColumnName(column));
  371. if (column == IncomingChangesTreeColumn.Path)
  372. {
  373. if (incomingChangesTree.HasMeta(item.ChangeInfo))
  374. label = string.Concat(label, UnityConstants.TREEVIEW_META_LABEL);
  375. Texture icon = GetIcon(wkPath, incomingChange);
  376. GetChangesOverlayIcon.Data overlayIconData =
  377. GetChangesOverlayIcon.ForGluonIncomingChange(
  378. incomingChange, isSolvedConflict);
  379. bool wasChecked = incomingChange.IsChecked();
  380. bool isChecked = DrawTreeViewItem.ForCheckableItemCell(
  381. rect, rowHeight, item.depth,
  382. icon, overlayIconData, label,
  383. isSelected, isFocused, isCurrentConflict,
  384. wasChecked);
  385. incomingChange.UpdateCheckedState(isChecked);
  386. if (wasChecked != isChecked)
  387. {
  388. UpdateCheckStateForSelection(treeView, item);
  389. onCheckedNodeChanged();
  390. }
  391. return;
  392. }
  393. if (column == IncomingChangesTreeColumn.Size)
  394. {
  395. DrawTreeViewItem.ForSecondaryLabelRightAligned(
  396. rect, label, isSelected, isFocused, isCurrentConflict);
  397. return;
  398. }
  399. DrawTreeViewItem.ForSecondaryLabel(
  400. rect, label, isSelected, isFocused, isCurrentConflict);
  401. }
  402. static Texture GetCategoryIcon(
  403. IncomingChangeCategory.Type categoryType)
  404. {
  405. switch (categoryType)
  406. {
  407. case IncomingChangeCategory.Type.Conflicted:
  408. return Images.GetImage(Images.Name.IconMergeConflict);
  409. case IncomingChangeCategory.Type.Changed:
  410. return Images.GetImage(Images.Name.IconChanged);
  411. case IncomingChangeCategory.Type.Moved:
  412. return Images.GetImage(Images.Name.IconMoved);
  413. case IncomingChangeCategory.Type.Deleted:
  414. return Images.GetImage(Images.Name.IconDeleted);
  415. case IncomingChangeCategory.Type.Added:
  416. return Images.GetImage(Images.Name.IconAdded);
  417. default:
  418. return null;
  419. }
  420. }
  421. static Texture GetIcon(
  422. string wkPath,
  423. IncomingChangeInfo incomingChange)
  424. {
  425. bool isDirectory = incomingChange.GetRevision().
  426. Type == EnumRevisionType.enDirectory;
  427. if (isDirectory || incomingChange.IsXLink())
  428. return Images.GetDirectoryIcon();
  429. string fullPath = WorkspacePath.GetWorkspacePathFromCmPath(
  430. wkPath, incomingChange.GetPath(), Path.DirectorySeparatorChar);
  431. return Images.GetFileIcon(fullPath);
  432. }
  433. static GUIStyle GetCategoryStyle(
  434. IncomingChangeCategory category,
  435. int solvedConflictsCount,
  436. bool isSelected)
  437. {
  438. if (isSelected)
  439. return UnityStyles.Tree.Label;
  440. if (category.CategoryType != IncomingChangeCategory.Type.Conflicted)
  441. return UnityStyles.Tree.Label;
  442. return category.GetChildrenCount() > solvedConflictsCount ?
  443. UnityStyles.Tree.RedLabel : UnityStyles.Tree.GreenLabel;
  444. }
  445. static bool ShouldExpandCategory(
  446. IncomingChangesTreeView treeView,
  447. ChangeCategoryTreeViewItem categoryTreeViewItem,
  448. int categoriesCount,
  449. bool expandCategories)
  450. {
  451. if (expandCategories)
  452. {
  453. if (categoriesCount == 1)
  454. return true;
  455. if (categoryTreeViewItem.Category.CategoryType
  456. == IncomingChangeCategory.Type.Conflicted)
  457. return true;
  458. if (categoryTreeViewItem.Category.GetChildrenCount()
  459. > NODES_TO_EXPAND_CATEGORY)
  460. return false;
  461. return true;
  462. }
  463. return treeView.IsExpanded(categoryTreeViewItem.id);
  464. }
  465. bool mExpandCategories;
  466. TreeViewItemIds<IncomingChangeCategory, IncomingChangeInfo> mTreeViewItemIds =
  467. new TreeViewItemIds<IncomingChangeCategory, IncomingChangeInfo>();
  468. List<TreeViewItem> mRows = new List<TreeViewItem>();
  469. IncomingChangeInfo mCurrentConflict;
  470. List<IncomingChangeInfo> mSolvedConflicts;
  471. UnityIncomingChangesTree mIncomingChangesTree;
  472. readonly Action mOnCheckedNodeChanged;
  473. readonly IncomingChangesViewMenu mMenu;
  474. readonly List<string> mColumnNames;
  475. readonly WorkspaceInfo mWkInfo;
  476. const int NODES_TO_EXPAND_CATEGORY = 10;
  477. }
  478. }