FlagSelector.cs 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240
  1. namespace Terminal.Gui.Views;
  2. // DoubleClick - Focus, Select (Toggle), and Accept the item under the mouse.
  3. // Click - Focus, Select (Toggle), and do NOT Accept the item under the mouse.
  4. // Not Focused:
  5. // HotKey - Restore Focus. Do NOT change Active.
  6. // Item HotKey - Focus item. Activate (Toggle) item. Do NOT Accept.
  7. // Focused:
  8. // Space key - Activate (Toggle) focused item. Do NOT Accept.
  9. // Enter key - Activate (Toggle) and Accept the focused item.
  10. // HotKey - No-op.
  11. // Item HotKey - Focus item, Activate (Toggle), and do NOT Accept.
  12. /// <summary>
  13. /// Provides a user interface for displaying and selecting non-mutually-exclusive flags from a provided dictionary.
  14. /// <see cref="FlagSelector{TFlagsEnum}"/> provides a type-safe version where a `[Flags]` <see langword="enum"/> can be
  15. /// provided.
  16. /// </summary>
  17. public class FlagSelector : SelectorBase, IDesignable
  18. {
  19. /// <inheritdoc />
  20. protected override void OnSubViewAdded (View view)
  21. {
  22. base.OnSubViewAdded (view);
  23. if (view is not CheckBox checkbox)
  24. {
  25. return;
  26. }
  27. checkbox.RadioStyle = false;
  28. checkbox.CheckedStateChanging += OnCheckboxOnCheckedStateChanging;
  29. checkbox.CheckedStateChanged += OnCheckboxOnCheckedStateChanged;
  30. checkbox.Activating += OnCheckboxOnActivating;
  31. checkbox.Accepting += OnCheckboxOnAccepting;
  32. }
  33. private void OnCheckboxOnCheckedStateChanging (object? sender, ResultEventArgs<CheckState> args)
  34. {
  35. if (sender is not CheckBox checkbox)
  36. {
  37. return;
  38. }
  39. if (checkbox.CheckedState == CheckState.Checked && (int)checkbox.Data! == 0 && Value == 0)
  40. {
  41. args.Handled = true;
  42. }
  43. }
  44. private void OnCheckboxOnCheckedStateChanged (object? sender, EventArgs<CheckState> args)
  45. {
  46. if (sender is not CheckBox checkbox)
  47. {
  48. return;
  49. }
  50. int newValue = Value ?? 0;
  51. if (checkbox.CheckedState == CheckState.Checked)
  52. {
  53. if ((int)checkbox.Data! == default!)
  54. {
  55. newValue = 0;
  56. }
  57. else
  58. {
  59. newValue |= (int)checkbox.Data!;
  60. }
  61. }
  62. else
  63. {
  64. newValue &= ~(int)checkbox.Data!;
  65. }
  66. Value = newValue;
  67. }
  68. private void OnCheckboxOnActivating (object? sender, CommandEventArgs args)
  69. {
  70. if (sender is not CheckBox checkbox)
  71. {
  72. return;
  73. }
  74. if (checkbox.CanFocus)
  75. {
  76. // For Activate, if the view is focusable and SetFocus succeeds, by definition,
  77. // the event is handled. So return what SetFocus returns.
  78. checkbox.SetFocus ();
  79. }
  80. // Activating doesn't normally propagate, so we do it here
  81. if (InvokeCommand (Command.Activate, args.Context) is true)
  82. {
  83. // Do not return here; we want to toggle the checkbox state
  84. args.Handled = true;
  85. //return;
  86. }
  87. }
  88. private void OnCheckboxOnAccepting (object? sender, CommandEventArgs args)
  89. {
  90. if (sender is not CheckBox checkbox)
  91. {
  92. return;
  93. }
  94. Value = (int)checkbox.Data!;
  95. args.Handled = false; // Do not set to false; let Accepting propagate
  96. }
  97. private int? _value;
  98. /// <summary>
  99. /// Gets or sets the value of the selected flags.
  100. /// </summary>
  101. public override int? Value
  102. {
  103. get => _value;
  104. set
  105. {
  106. if (_updatingChecked || _value == value)
  107. {
  108. return;
  109. }
  110. int? previousValue = _value;
  111. _value = value;
  112. if (_value is null)
  113. {
  114. UncheckNone ();
  115. UncheckAll ();
  116. }
  117. else
  118. {
  119. UpdateChecked ();
  120. }
  121. RaiseValueChanged (previousValue);
  122. }
  123. }
  124. private void UncheckNone ()
  125. {
  126. // Uncheck ONLY the None checkbox (Data == 0)
  127. _updatingChecked = true;
  128. foreach (CheckBox cb in SubViews.OfType<CheckBox> ().Where (sv => (int)sv.Data! == 0))
  129. {
  130. cb.CheckedState = CheckState.UnChecked;
  131. }
  132. _updatingChecked = false;
  133. }
  134. private void UncheckAll ()
  135. {
  136. // Uncheck all NON-None checkboxes (Data != 0)
  137. _updatingChecked = true;
  138. foreach (CheckBox cb in SubViews.OfType<CheckBox> ().Where (sv => (int)(sv.Data ?? default!) != default!))
  139. {
  140. cb.CheckedState = CheckState.UnChecked;
  141. }
  142. _updatingChecked = false;
  143. }
  144. private bool _updatingChecked = false;
  145. /// <inheritdoc />
  146. public override void UpdateChecked ()
  147. {
  148. if (_updatingChecked)
  149. {
  150. return;
  151. }
  152. _updatingChecked = true;
  153. foreach (CheckBox cb in SubViews.OfType<CheckBox> ())
  154. {
  155. var flag = (int)(cb.Data ?? throw new InvalidOperationException ("CheckBox.Data must be set"));
  156. // If this flag is set in Value, check the checkbox. Otherwise, uncheck it.
  157. if (flag == 0)
  158. {
  159. cb.CheckedState = (Value != 0) ? CheckState.UnChecked : CheckState.Checked;
  160. }
  161. else
  162. {
  163. cb.CheckedState = (Value & flag) == flag ? CheckState.Checked : CheckState.UnChecked;
  164. }
  165. }
  166. _updatingChecked = false;
  167. }
  168. /// <inheritdoc />
  169. protected override void OnCreatingSubViews ()
  170. {
  171. // FlagSelector supports a "None" check box; add it
  172. if (Styles.HasFlag (SelectorStyles.ShowNoneFlag) && Values is { } && !Values.Contains (0))
  173. {
  174. Add (CreateCheckBox ("None", 0));
  175. }
  176. }
  177. /// <inheritdoc />
  178. protected override void OnCreatedSubViews ()
  179. {
  180. // If the values include 0, and ShowNoneFlag is not specified, remove the "None" check box
  181. if (!Styles.HasFlag (SelectorStyles.ShowNoneFlag))
  182. {
  183. CheckBox? noneCheckBox = SubViews.OfType<CheckBox> ().FirstOrDefault (cb => (int)cb.Data! == 0);
  184. if (noneCheckBox is { })
  185. {
  186. Remove (noneCheckBox);
  187. noneCheckBox.Dispose ();
  188. }
  189. }
  190. }
  191. /// <inheritdoc/>
  192. public bool EnableForDesign ()
  193. {
  194. Styles = SelectorStyles.All;
  195. AssignHotKeys = true;
  196. SetValuesAndLabels<SelectorStyles> ();
  197. Labels = Enum.GetValues<SelectorStyles> ()
  198. .Select (
  199. l => l switch
  200. {
  201. SelectorStyles.None => "No Style",
  202. SelectorStyles.ShowNoneFlag => "Show None Value Style",
  203. SelectorStyles.ShowValue => "Show Value Editor Style",
  204. SelectorStyles.All => "All Styles",
  205. _ => l.ToString ()
  206. }).ToList ();
  207. return true;
  208. }
  209. }