2
0

TreeTableSource.cs 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205
  1. #nullable disable
  2. namespace Terminal.Gui.Views;
  3. /// <summary>An <see cref="ITableSource"/> with expandable rows.</summary>
  4. /// <typeparam name="T"></typeparam>
  5. public class TreeTableSource<T> : IEnumerableTableSource<T>, IDisposable where T : class
  6. {
  7. private readonly Dictionary<string, Func<T, object>> _lambdas;
  8. private readonly TableView _tableView;
  9. private readonly TreeView<T> _tree;
  10. /// <summary>
  11. /// Creates a new instance of <see cref="TreeTableSource{T}"/> presenting the given <paramref name="tree"/>. This
  12. /// source should only be used with <paramref name="table"/>.
  13. /// </summary>
  14. /// <param name="table">The table this source will provide data for.</param>
  15. /// <param name="firstColumnName">
  16. /// Column name to use for the first column of the table (where the tree branches/leaves will
  17. /// be rendered.
  18. /// </param>
  19. /// <param name="tree">
  20. /// The tree data to render. This should be a new view and not used elsewhere (e.g. via
  21. /// <see cref="View.Add(View)"/>).
  22. /// </param>
  23. /// <param name="subsequentColumns">
  24. /// Getter methods for each additional property you want to present in the table. For example:
  25. /// <code>
  26. /// new () {
  27. /// { "Colname1", (t)=>t.SomeField},
  28. /// { "Colname2", (t)=>t.SomeOtherField}
  29. /// }
  30. /// </code>
  31. /// </param>
  32. public TreeTableSource (
  33. TableView table,
  34. string firstColumnName,
  35. TreeView<T> tree,
  36. Dictionary<string, Func<T, object>> subsequentColumns
  37. )
  38. {
  39. _tableView = table;
  40. _tree = tree;
  41. _tableView.KeyDown += Table_KeyPress;
  42. _tableView.MouseClick += Table_MouseClick;
  43. List<string> colList = subsequentColumns.Keys.ToList ();
  44. colList.Insert (0, firstColumnName);
  45. ColumnNames = colList.ToArray ();
  46. _lambdas = subsequentColumns;
  47. }
  48. /// <inheritdoc/>
  49. public void Dispose ()
  50. {
  51. _tableView.KeyDown -= Table_KeyPress;
  52. _tableView.MouseClick -= Table_MouseClick;
  53. _tree.Dispose ();
  54. }
  55. /// <inheritdoc/>
  56. public object this [int row, int col] =>
  57. col == 0 ? GetColumnZeroRepresentationFromTree (row) : _lambdas [ColumnNames [col]] (RowToObject (row));
  58. /// <inheritdoc/>
  59. public int Rows => _tree.BuildLineMap ().Count;
  60. /// <inheritdoc/>
  61. public int Columns => _lambdas.Count + 1;
  62. /// <inheritdoc/>
  63. public string [] ColumnNames { get; }
  64. /// <inheritdoc/>
  65. public T GetObjectOnRow (int row) { return RowToObject (row); }
  66. /// <inheritdoc/>
  67. public IEnumerable<T> GetAllObjects () { return _tree.BuildLineMap ().Select (b => b.Model); }
  68. /// <summary>Returns the tree model object rendering on the given <paramref name="row"/> of the table.</summary>
  69. /// <param name="row">Row in table.</param>
  70. /// <returns></returns>
  71. public T RowToObject (int row) { return _tree.BuildLineMap ().ElementAt (row).Model; }
  72. private string GetColumnZeroRepresentationFromTree (int row)
  73. {
  74. Branch<T> branch = RowToBranch (row);
  75. // Everything on the line before the expansion run and branch text
  76. string [] prefix = branch.GetLinePrefix ().ToArray ();
  77. string expansion = branch.GetExpandableSymbol ();
  78. string lineBody = _tree.AspectGetter (branch.Model) ?? "";
  79. var sb = new StringBuilder ();
  80. foreach (string p in prefix)
  81. {
  82. sb.Append (p);
  83. }
  84. sb.Append (expansion);
  85. sb.Append (lineBody);
  86. return sb.ToString ();
  87. }
  88. private bool IsInTreeColumn (int column, bool isKeyboard)
  89. {
  90. string [] colNames = _tableView.Table.ColumnNames;
  91. if (column < 0 || column >= colNames.Length)
  92. {
  93. return false;
  94. }
  95. // if full row is selected then it is hard to tell which sub cell in the tree
  96. // has focus so we should typically just always respond with expand/collapse
  97. if (_tableView.FullRowSelect && isKeyboard)
  98. {
  99. return true;
  100. }
  101. // we cannot just check that SelectedColumn is 0 because source may
  102. // be wrapped e.g. with a CheckBoxTableSourceWrapperBase
  103. return colNames [column] == ColumnNames [0];
  104. }
  105. private Branch<T> RowToBranch (int row) { return _tree.BuildLineMap ().ElementAt (row); }
  106. private void Table_KeyPress (object sender, Key e)
  107. {
  108. if (!IsInTreeColumn (_tableView.SelectedColumn, true))
  109. {
  110. return;
  111. }
  112. T obj = _tree.GetObjectOnRow (_tableView.SelectedRow);
  113. if (obj is null)
  114. {
  115. return;
  116. }
  117. if (e.KeyCode == KeyCode.CursorLeft)
  118. {
  119. if (_tree.IsExpanded (obj))
  120. {
  121. _tree.Collapse (obj);
  122. e.Handled = true;
  123. }
  124. }
  125. if (e.KeyCode == KeyCode.CursorRight)
  126. {
  127. if (_tree.CanExpand (obj) && !_tree.IsExpanded (obj))
  128. {
  129. _tree.Expand (obj);
  130. e.Handled = true;
  131. }
  132. }
  133. if (e.Handled)
  134. {
  135. _tree.InvalidateLineMap ();
  136. _tableView.SetNeedsDraw ();
  137. }
  138. }
  139. private void Table_MouseClick (object sender, MouseEventArgs e)
  140. {
  141. Point? hit = _tableView.ScreenToCell (e.Position.X, e.Position.Y, out int? headerIfAny, out int? offsetX);
  142. if (hit is null || headerIfAny is { } || !IsInTreeColumn (hit.Value.X, false) || offsetX is null)
  143. {
  144. return;
  145. }
  146. Branch<T> branch = RowToBranch (hit.Value.Y);
  147. if (branch.IsHitOnExpandableSymbol (offsetX.Value))
  148. {
  149. T m = branch.Model;
  150. if (_tree.CanExpand (m) && !_tree.IsExpanded (m))
  151. {
  152. _tree.Expand (m);
  153. e.Handled = true;
  154. }
  155. else if (_tree.IsExpanded (m))
  156. {
  157. _tree.Collapse (m);
  158. e.Handled = true;
  159. }
  160. }
  161. if (e.Handled)
  162. {
  163. _tree.InvalidateLineMap ();
  164. _tableView.SetNeedsDraw ();
  165. }
  166. }
  167. }