CheckBoxTableSourceWrapper.cs 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193
  1. 
  2. namespace Terminal.Gui.Views;
  3. /// <summary>
  4. /// <see cref="ITableSource"/> for a <see cref="TableView"/> which adds a checkbox column as an additional column
  5. /// in the table.
  6. /// </summary>
  7. /// <remarks>
  8. /// This class wraps another <see cref="ITableSource"/> and dynamically serves its rows/cols plus an extra column.
  9. /// Data in the wrapped source can be dynamic (change over time).
  10. /// </remarks>
  11. public abstract class CheckBoxTableSourceWrapperBase : ITableSource
  12. {
  13. private readonly TableView tableView;
  14. /// <summary>
  15. /// Creates a new instance of the class presenting the data in <paramref name="toWrap"/> plus an additional
  16. /// checkbox column.
  17. /// </summary>
  18. /// <param name="tableView">
  19. /// The <see cref="TableView"/> this source will be used with. This is required for event
  20. /// registration.
  21. /// </param>
  22. /// <param name="toWrap">The original data source of the <see cref="TableView"/> that you want to add checkboxes to.</param>
  23. public CheckBoxTableSourceWrapperBase (TableView tableView, ITableSource toWrap)
  24. {
  25. Wrapping = toWrap;
  26. this.tableView = tableView;
  27. tableView.KeyBindings.ReplaceCommands (Key.Space, Command.Select);
  28. tableView.MouseClick += TableView_MouseClick;
  29. tableView.CellToggled += TableView_CellToggled;
  30. }
  31. /// <summary>
  32. /// Gets or sets the character to use for checked entries. Defaults to <see cref="Glyphs.CheckStateChecked"/>
  33. /// </summary>
  34. public Rune CheckedRune { get; set; } = Glyphs.CheckStateChecked;
  35. /// <summary>
  36. /// Gets or sets the character to use for checked entry when <see cref="UseRadioButtons"/> is true. Defaults to
  37. /// <see cref="Glyphs.Selected"/>
  38. /// </summary>
  39. public Rune RadioCheckedRune { get; set; } = Glyphs.Selected;
  40. /// <summary>
  41. /// Gets or sets the character to use for unchecked entries when <see cref="UseRadioButtons"/> is true. Defaults
  42. /// to <see cref="Glyphs.UnSelected"/>
  43. /// </summary>
  44. public Rune RadioUnCheckedRune { get; set; } = Glyphs.UnSelected;
  45. /// <summary>
  46. /// Gets or sets the character to use for UnChecked entries. Defaults to <see cref="Glyphs.CheckStateUnChecked"/>
  47. /// </summary>
  48. public Rune UnCheckedRune { get; set; } = Glyphs.CheckStateUnChecked;
  49. /// <summary>Gets or sets whether to only allow a single row to be toggled at once (Radio button).</summary>
  50. public bool UseRadioButtons { get; set; }
  51. /// <summary>Gets the <see cref="ITableSource"/> that this instance is wrapping.</summary>
  52. public ITableSource Wrapping { get; }
  53. /// <inheritdoc/>
  54. public object this [int row, int col]
  55. {
  56. get
  57. {
  58. if (col == 0)
  59. {
  60. if (UseRadioButtons)
  61. {
  62. return IsChecked (row) ? RadioCheckedRune : RadioUnCheckedRune;
  63. }
  64. return IsChecked (row) ? CheckedRune : UnCheckedRune;
  65. }
  66. return Wrapping [row, col - 1];
  67. }
  68. }
  69. /// <inheritdoc/>
  70. public int Rows => Wrapping.Rows;
  71. /// <inheritdoc/>
  72. public int Columns => Wrapping.Columns + 1;
  73. /// <inheritdoc/>
  74. public string [] ColumnNames
  75. {
  76. get
  77. {
  78. List<string> toReturn = Wrapping.ColumnNames.ToList ();
  79. toReturn.Insert (0, " ");
  80. return toReturn.ToArray ();
  81. }
  82. }
  83. /// <summary>Clears the toggled state of all rows.</summary>
  84. protected abstract void ClearAllToggles ();
  85. /// <summary>Returns true if <paramref name="row"/> is checked.</summary>
  86. /// <param name="row"></param>
  87. /// <returns></returns>
  88. protected abstract bool IsChecked (int row);
  89. /// <summary>
  90. /// Called when the 'toggled all' action is performed. This should change state from 'some selected' to 'all
  91. /// selected' or clear selection if all area already selected.
  92. /// </summary>
  93. protected abstract void ToggleAllRows ();
  94. /// <summary>Flips the checked state of the given <paramref name="row"/>/</summary>
  95. /// <param name="row"></param>
  96. protected abstract void ToggleRow (int row);
  97. /// <summary>
  98. /// Flips the checked state for a collection of rows. If some (but not all) are selected they should flip to all
  99. /// selected.
  100. /// </summary>
  101. /// <param name="range"></param>
  102. protected abstract void ToggleRows (int [] range);
  103. private void TableView_CellToggled (object sender, CellToggledEventArgs e)
  104. {
  105. // Suppress default toggle behavior when using checkboxes
  106. // and instead handle ourselves
  107. int [] range = tableView.GetAllSelectedCells ().Select (c => c.Y).Distinct ().ToArray ();
  108. if (UseRadioButtons)
  109. {
  110. // multi selection makes it unclear what to toggle in this situation
  111. if (range.Length != 1)
  112. {
  113. e.Cancel = true;
  114. return;
  115. }
  116. ClearAllToggles ();
  117. ToggleRow (range.Single ());
  118. }
  119. else
  120. {
  121. ToggleRows (range);
  122. }
  123. e.Cancel = true;
  124. tableView.SetNeedsDraw ();
  125. }
  126. private void TableView_MouseClick (object sender, MouseEventArgs e)
  127. {
  128. // we only care about clicks (not movements)
  129. if (!e.Flags.HasFlag (MouseFlags.Button1Clicked))
  130. {
  131. return;
  132. }
  133. Point? hit = tableView.ScreenToCell (e.Position.X, e.Position.Y, out int? headerIfAny);
  134. if (headerIfAny.HasValue && headerIfAny.Value == 0)
  135. {
  136. // clicking in header with radio buttons does nothing
  137. if (UseRadioButtons)
  138. {
  139. return;
  140. }
  141. // otherwise it ticks all rows
  142. ToggleAllRows ();
  143. e.Handled = true;
  144. tableView.SetNeedsDraw ();
  145. }
  146. else if (hit.HasValue && hit.Value.X == 0)
  147. {
  148. if (UseRadioButtons)
  149. {
  150. ClearAllToggles ();
  151. ToggleRow (hit.Value.Y);
  152. }
  153. else
  154. {
  155. ToggleRow (hit.Value.Y);
  156. }
  157. e.Handled = true;
  158. tableView.SetNeedsDraw ();
  159. }
  160. }
  161. }