CheckBoxTableSourceWrapper.cs 5.6 KB

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