TreeTableSource.cs 6.1 KB

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