TreeTableSource.cs 5.2 KB

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