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;
}
}