CheckBoxTableSourceWrapper.cs 5.5 KB

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