ListTableSource.cs 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199
  1. using NStack;
  2. using System;
  3. using System.Collections;
  4. using System.Data;
  5. using System.Linq;
  6. namespace Terminal.Gui {
  7. /// <summary>
  8. /// <see cref="ITableSource"/> implementation that wraps
  9. /// a <see cref="System.Collections.IList"/>. This class is
  10. /// mutable: changes are permitted to the wrapped <see cref="IList"/>.
  11. /// </summary>
  12. public class ListTableSource : ITableSource {
  13. /// <summary>
  14. /// The list this source wraps.
  15. /// </summary>
  16. public IList List;
  17. /// <summary>
  18. /// The style this source uses.
  19. /// </summary>
  20. public ListColumnStyle Style;
  21. /// <summary>
  22. /// The data table this source wraps.
  23. /// </summary>
  24. public DataTable DataTable { get; private set; }
  25. private TableView _tableView;
  26. private Rect _lastBounds;
  27. private int _lastMaxCellWidth;
  28. private int _lastMinCellWidth;
  29. private ListColumnStyle _lastStyle;
  30. private IList _lastList;
  31. /// <summary>
  32. /// Creates a new columned list table instance based on the data in <paramref name="list"/>
  33. /// and dimensions from <paramref name="tableView"/>.
  34. /// </summary>
  35. /// <param name="list"></param>
  36. /// <param name="tableView"></param>
  37. /// <param name="style"></param>
  38. public ListTableSource (IList list, TableView tableView, ListColumnStyle style)
  39. {
  40. this.List = list;
  41. this._tableView = tableView;
  42. Style = style;
  43. this.DataTable = CreateTable (CalculateColumns ());
  44. // TODO: Determine the best event for this
  45. tableView.DrawContent += TableView_DrawContent;
  46. }
  47. /// <inheritdoc/>
  48. public ListTableSource (IList list, TableView tableView) : this (list, tableView, new ListColumnStyle ()) { }
  49. private void TableView_DrawContent (object sender, DrawEventArgs e)
  50. {
  51. if ((!_tableView.Bounds.Equals (_lastBounds)) ||
  52. _tableView.MaxCellWidth != _lastMaxCellWidth ||
  53. _tableView.MinCellWidth != _lastMinCellWidth ||
  54. Style != _lastStyle ||
  55. this.List != _lastList) {
  56. this.DataTable = CreateTable (CalculateColumns ());
  57. }
  58. _lastBounds = _tableView.Bounds;
  59. _lastMinCellWidth = _tableView.MaxCellWidth;
  60. _lastMaxCellWidth = _tableView.MaxCellWidth;
  61. _lastStyle = Style;
  62. _lastList = this.List;
  63. }
  64. /// <inheritdoc/>
  65. public object this [int row, int col] {
  66. get {
  67. int idx;
  68. if (Style.Orientation == Orientation.Vertical) {
  69. idx = (col * Rows) + row;
  70. } else {
  71. idx = (row * Columns) + col;
  72. }
  73. if (idx < 0 || idx >= Count) {
  74. return null;
  75. }
  76. return this.List [idx];
  77. }
  78. }
  79. /// <summary>
  80. /// The number of items in the IList source
  81. /// </summary>
  82. public int Count => this.List.Count;
  83. /// <inheritdoc/>
  84. public int Rows => this.DataTable.Rows.Count;
  85. /// <inheritdoc/>
  86. public int Columns => this.DataTable.Columns.Count;
  87. /// <inheritdoc/>
  88. public string [] ColumnNames => Enumerable.Range (0, Columns).Select (n => n.ToString ()).ToArray ();
  89. /// <summary>
  90. /// Creates a DataTable from an IList to display in a <see cref="TableView"/>
  91. /// </summary>
  92. private DataTable CreateTable (int cols = 1)
  93. {
  94. var table = new DataTable ();
  95. for (int col = 0; col < cols; col++) {
  96. table.Columns.Add (new DataColumn (col.ToString ()));
  97. }
  98. for (int row = 0; row < (Count / table.Columns.Count); row++) {
  99. table.Rows.Add ();
  100. }
  101. // return partial row
  102. if (Count % table.Columns.Count != 0) {
  103. table.Rows.Add ();
  104. }
  105. return table;
  106. }
  107. /// <summary>
  108. /// Returns the size in characters of the longest value read from <see cref="ListTableSource.List"/>
  109. /// </summary>
  110. /// <returns></returns>
  111. private int CalculateMaxLength ()
  112. {
  113. if (List == null || Count == 0) {
  114. return 0;
  115. }
  116. int maxLength = 0;
  117. foreach (var t in List) {
  118. int l;
  119. if (t is ustring u) {
  120. l = TextFormatter.GetTextWidth (u);
  121. } else if (t is string s) {
  122. l = s.Length;
  123. } else {
  124. l = t.ToString ().Length;
  125. }
  126. if (l > maxLength) {
  127. maxLength = l;
  128. }
  129. }
  130. return maxLength;
  131. }
  132. private int CalculateColumns ()
  133. {
  134. int cols;
  135. int colWidth = CalculateMaxLength ();
  136. if (colWidth > _tableView.MaxCellWidth) {
  137. colWidth = _tableView.MaxCellWidth;
  138. }
  139. if (_tableView.MinCellWidth > 0 && colWidth < _tableView.MinCellWidth) {
  140. if (_tableView.MinCellWidth > _tableView.MaxCellWidth) {
  141. colWidth = _tableView.MaxCellWidth;
  142. } else {
  143. colWidth = _tableView.MinCellWidth;
  144. }
  145. }
  146. if ((Style.Orientation == Orientation.Vertical) != Style.ScrollParallel) {
  147. float f = (float)_tableView.Bounds.Height - _tableView.GetHeaderHeight ();
  148. cols = (int)Math.Ceiling (Count / f);
  149. } else {
  150. cols = ((int)Math.Ceiling (((float)_tableView.Bounds.Width - 1) / colWidth)) - 2;
  151. }
  152. return (cols > 1) ? cols : 1;
  153. }
  154. /// <summary>
  155. /// Defines rendering options that affect how the view is displayed.
  156. /// </summary>
  157. public class ListColumnStyle {
  158. /// <summary>
  159. /// Gets or sets an Orientation enum indicating whether to populate data down each column
  160. /// rather than across each row. Defaults to <see cref="Orientation.Horizontal"/>.
  161. /// </summary>
  162. public Orientation Orientation { get; set; } = Orientation.Horizontal;
  163. /// <summary>
  164. /// Gets or sets a flag indicating whether to scroll in the same direction as <see cref="ListTableSource.ListColumnStyle.Orientation"/>.
  165. /// Defaults to <see langword="false"/>.
  166. /// </summary>
  167. public bool ScrollParallel { get; set; } = false;
  168. }
  169. }
  170. }