namespace Terminal.Gui.Views;
/// Shows a checkbox that can be cycled between two or three states.
///
///
/// is used to display radio button style glyphs (●) instead of checkbox style glyphs (☑).
///
///
public class CheckBox : View
{
private static MouseState _defaultHighlightStates = MouseState.PressedOutside | MouseState.Pressed | MouseState.In; // Resources/config.json overrides
///
/// Gets or sets the default Highlight Style.
///
[ConfigurationProperty (Scope = typeof (ThemeScope))]
public static MouseState DefaultHighlightStates
{
get => _defaultHighlightStates;
set => _defaultHighlightStates = value;
}
///
/// Initializes a new instance of .
///
public CheckBox ()
{
Width = Dim.Auto (DimAutoStyle.Text);
Height = Dim.Auto (DimAutoStyle.Text, 1);
CanFocus = true;
// Activate (Space key and single-click) - Raise Activate event and Advance
// - DO NOT raise Accept
// - DO NOT SetFocus
AddCommand (Command.Select, ActivateAndAdvance);
// Accept (Enter key and double-click) - Raise Accept event
// - DO NOT advance state
// The default Accept handler does that.
MouseBindings.Add (MouseFlags.Button1DoubleClicked, Command.Accept);
TitleChanged += Checkbox_TitleChanged;
HighlightStates = DefaultHighlightStates;
}
///
protected override bool OnHandlingHotKey (CommandEventArgs args)
{
// Invoke Activate on ourselves
if (InvokeCommand (Command.Select, args.Context) is true)
{
// Default behavior for View is to set Focus on hotkey. We need to return
// true here to indicate Activate was handled. That will prevent the default
// behavior from setting focus, so we do it here.
SetFocus ();
return true;
}
return base.OnHandlingHotKey (args);
}
private bool? ActivateAndAdvance (ICommandContext? commandContext)
{
if (RaiseSelecting (commandContext) is true)
{
return true;
}
bool? cancelled = AdvanceCheckState ();
if (cancelled is true)
{
return true;
}
return commandContext?.Command == Command.HotKey ? cancelled : cancelled is false;
}
private void Checkbox_TitleChanged (object? sender, EventArgs e)
{
base.Text = e.Value;
TextFormatter.HotKeySpecifier = HotKeySpecifier;
}
///
public override string Text
{
get => Title;
set => base.Text = Title = value;
}
///
public override Rune HotKeySpecifier
{
get => base.HotKeySpecifier;
set => TextFormatter.HotKeySpecifier = base.HotKeySpecifier = value;
}
private bool _allowNone;
///
/// If allows to be . The default is
/// .
///
public bool AllowCheckStateNone
{
get => _allowNone;
set
{
if (_allowNone == value)
{
return;
}
_allowNone = value;
if (CheckedState == CheckState.None)
{
CheckedState = CheckState.UnChecked;
}
}
}
private CheckState _checkedState = CheckState.UnChecked;
///
/// The state of the .
///
///
///
/// If is and , the
///
/// will display the Glyphs.CheckStateNone character (☒).
///
///
/// If , the
/// will display the Glyphs.CheckStateUnChecked character (☐).
///
///
/// If , the
/// will display the Glyphs.CheckStateChecked character (☑).
///
///
public CheckState CheckedState
{
get => _checkedState;
set => ChangeCheckedState (value);
}
///
/// INTERNAL Sets CheckedState.
///
///
///
/// if state change was canceled, if the state changed, and
/// if the state was not changed for some other reason.
///
private bool? ChangeCheckedState (CheckState value)
{
if (_checkedState == value || (value is CheckState.None && !AllowCheckStateNone))
{
return null;
}
ResultEventArgs e = new (value);
if (OnCheckedStateChanging (e))
{
return true;
}
CheckedStateChanging?.Invoke (this, e);
if (e.Handled)
{
return e.Handled;
}
_checkedState = value;
UpdateTextFormatterText ();
SetNeedsLayout ();
EventArgs args = new (in _checkedState);
OnCheckedStateChanged (args);
CheckedStateChanged?.Invoke (this, args);
return false;
}
/// Called when the state is changing.
///
///
/// The state change can be cancelled by setting the args.Cancel to .
///
///
protected virtual bool OnCheckedStateChanging (ResultEventArgs args) { return false; }
/// Raised when the state is changing.
///
///
/// This event can be cancelled. If cancelled, the will not change its state.
///
///
public event EventHandler>? CheckedStateChanging;
/// Called when the state has changed.
protected virtual void OnCheckedStateChanged (EventArgs args) { }
/// Raised when the state has changed.
public event EventHandler>? CheckedStateChanged;
///
/// Advances to the next value. Invokes the cancelable
/// event.
///
///
///
/// Cycles through the states , , and
/// .
///
///
/// If the event is not canceled, the will be updated
/// and the event will be raised.
///
///
///
/// if state change was canceled, if the state changed, and
/// if the state was not changed for some other reason.
///
public bool? AdvanceCheckState ()
{
CheckState oldValue = CheckedState;
ResultEventArgs e = new (oldValue);
switch (CheckedState)
{
case CheckState.None:
e.Result = CheckState.Checked;
break;
case CheckState.Checked:
e.Result = CheckState.UnChecked;
break;
case CheckState.UnChecked:
if (AllowCheckStateNone)
{
e.Result = CheckState.None;
}
else
{
e.Result = CheckState.Checked;
}
break;
}
bool? cancelled = ChangeCheckedState (e.Result);
return cancelled;
}
///
protected override bool OnClearingViewport ()
{
SetAttributeForRole (HasFocus ? VisualRole.Focus : VisualRole.Normal);
return base.OnClearingViewport ();
}
///
protected override void UpdateTextFormatterText ()
{
base.UpdateTextFormatterText ();
Rune glyph = RadioStyle ? GetRadioGlyph () : GetCheckGlyph ();
switch (TextAlignment)
{
case Alignment.Start:
case Alignment.Center:
case Alignment.Fill:
TextFormatter.Text = $"{glyph} {Text}";
break;
case Alignment.End:
TextFormatter.Text = $"{Text} {glyph}";
break;
}
}
private Rune GetCheckGlyph ()
{
return CheckedState switch
{
CheckState.Checked => Glyphs.CheckStateChecked,
CheckState.UnChecked => Glyphs.CheckStateUnChecked,
CheckState.None => Glyphs.CheckStateNone,
_ => throw new ArgumentOutOfRangeException ()
};
}
///
/// If , the will display radio button style glyphs (●) instead of
/// checkbox style glyphs (☑).
///
public bool RadioStyle { get; set; }
private Rune GetRadioGlyph ()
{
return CheckedState switch
{
CheckState.Checked => Glyphs.Selected,
CheckState.UnChecked => Glyphs.UnSelected,
CheckState.None => Glyphs.Dot,
_ => throw new ArgumentOutOfRangeException ()
};
}
}