namespace Terminal.Gui.Views; // DoubleClick - Focus, Select (Toggle), and Accept the item under the mouse. // Click - Focus, Select (Toggle), and do NOT Accept the item under the mouse. // Not Focused: // HotKey - Restore Focus. Do NOT change Active. // Item HotKey - Focus item. Activate (Toggle) item. Do NOT Accept. // Focused: // Space key - Activate (Toggle) focused item. Do NOT Accept. // Enter key - Activate (Toggle) and Accept the focused item. // HotKey - No-op. // Item HotKey - Focus item, Activate (Toggle), and do NOT Accept. /// /// Provides a user interface for displaying and selecting non-mutually-exclusive flags from a provided dictionary. /// provides a type-safe version where a `[Flags]` can be /// provided. /// public class FlagSelector : SelectorBase, IDesignable { /// protected override void OnSubViewAdded (View view) { base.OnSubViewAdded (view); if (view is not CheckBox checkbox) { return; } checkbox.RadioStyle = false; checkbox.CheckedStateChanging += OnCheckboxOnCheckedStateChanging; checkbox.CheckedStateChanged += OnCheckboxOnCheckedStateChanged; checkbox.Activating += OnCheckboxOnActivating; checkbox.Accepting += OnCheckboxOnAccepting; } private void OnCheckboxOnCheckedStateChanging (object? sender, ResultEventArgs args) { if (sender is not CheckBox checkbox) { return; } if (checkbox.CheckedState == CheckState.Checked && (int)checkbox.Data! == 0 && Value == 0) { args.Handled = true; } } private void OnCheckboxOnCheckedStateChanged (object? sender, EventArgs args) { if (sender is not CheckBox checkbox) { return; } int newValue = Value ?? 0; if (checkbox.CheckedState == CheckState.Checked) { if ((int)checkbox.Data! == default!) { newValue = 0; } else { newValue |= (int)checkbox.Data!; } } else { newValue &= ~(int)checkbox.Data!; } Value = newValue; } private void OnCheckboxOnActivating (object? sender, CommandEventArgs args) { if (sender is not CheckBox checkbox) { return; } if (checkbox.CanFocus) { // For Activate, if the view is focusable and SetFocus succeeds, by definition, // the event is handled. So return what SetFocus returns. checkbox.SetFocus (); } // Activating doesn't normally propagate, so we do it here if (InvokeCommand (Command.Activate, args.Context) is true) { // Do not return here; we want to toggle the checkbox state args.Handled = true; //return; } } private void OnCheckboxOnAccepting (object? sender, CommandEventArgs args) { if (sender is not CheckBox checkbox) { return; } Value = (int)checkbox.Data!; args.Handled = false; // Do not set to false; let Accepting propagate } private int? _value; /// /// Gets or sets the value of the selected flags. /// public override int? Value { get => _value; set { if (_updatingChecked || _value == value) { return; } int? previousValue = _value; _value = value; if (_value is null) { UncheckNone (); UncheckAll (); } else { UpdateChecked (); } RaiseValueChanged (previousValue); } } private void UncheckNone () { // Uncheck ONLY the None checkbox (Data == 0) _updatingChecked = true; foreach (CheckBox cb in SubViews.OfType ().Where (sv => (int)sv.Data! == 0)) { cb.CheckedState = CheckState.UnChecked; } _updatingChecked = false; } private void UncheckAll () { // Uncheck all NON-None checkboxes (Data != 0) _updatingChecked = true; foreach (CheckBox cb in SubViews.OfType ().Where (sv => (int)(sv.Data ?? default!) != default!)) { cb.CheckedState = CheckState.UnChecked; } _updatingChecked = false; } private bool _updatingChecked = false; /// public override void UpdateChecked () { if (_updatingChecked) { return; } _updatingChecked = true; foreach (CheckBox cb in SubViews.OfType ()) { var flag = (int)(cb.Data ?? throw new InvalidOperationException ("CheckBox.Data must be set")); // If this flag is set in Value, check the checkbox. Otherwise, uncheck it. if (flag == 0) { cb.CheckedState = (Value != 0) ? CheckState.UnChecked : CheckState.Checked; } else { cb.CheckedState = (Value & flag) == flag ? CheckState.Checked : CheckState.UnChecked; } } _updatingChecked = false; } /// protected override void OnCreatingSubViews () { // FlagSelector supports a "None" check box; add it if (Styles.HasFlag (SelectorStyles.ShowNoneFlag) && Values is { } && !Values.Contains (0)) { Add (CreateCheckBox ("None", 0)); } } /// protected override void OnCreatedSubViews () { // If the values include 0, and ShowNoneFlag is not specified, remove the "None" check box if (!Styles.HasFlag (SelectorStyles.ShowNoneFlag)) { CheckBox? noneCheckBox = SubViews.OfType ().FirstOrDefault (cb => (int)cb.Data! == 0); if (noneCheckBox is { }) { Remove (noneCheckBox); noneCheckBox.Dispose (); } } } /// public bool EnableForDesign () { Styles = SelectorStyles.All; AssignHotKeys = true; SetValuesAndLabels (); Labels = Enum.GetValues () .Select ( l => l switch { SelectorStyles.None => "No Style", SelectorStyles.ShowNoneFlag => "Show None Value Style", SelectorStyles.ShowValue => "Show Value Editor Style", SelectorStyles.All => "All Styles", _ => l.ToString () }).ToList (); return true; } }