namespace Terminal.Gui; /// /// for a which adds a checkbox column as an additional column /// in the table. /// /// /// This class wraps another and dynamically serves its rows/cols plus an extra column. /// Data in the wrapped source can be dynamic (change over time). /// public abstract class CheckBoxTableSourceWrapperBase : ITableSource { private readonly TableView tableView; /// /// Creates a new instance of the class presenting the data in plus an additional /// checkbox column. /// /// /// The this source will be used with. This is required for event /// registration. /// /// The original data source of the that you want to add checkboxes to. public CheckBoxTableSourceWrapperBase (TableView tableView, ITableSource toWrap) { Wrapping = toWrap; this.tableView = tableView; tableView.KeyBindings.ReplaceCommands (Key.Space, Command.Select); tableView.MouseClick += TableView_MouseClick; tableView.CellToggled += TableView_CellToggled; } /// /// Gets or sets the character to use for checked entries. Defaults to /// public Rune CheckedRune { get; set; } = Glyphs.CheckStateChecked; /// /// Gets or sets the character to use for checked entry when is true. Defaults to /// /// public Rune RadioCheckedRune { get; set; } = Glyphs.Selected; /// /// Gets or sets the character to use for unchecked entries when is true. Defaults /// to /// public Rune RadioUnCheckedRune { get; set; } = Glyphs.UnSelected; /// /// Gets or sets the character to use for UnChecked entries. Defaults to /// public Rune UnCheckedRune { get; set; } = Glyphs.CheckStateUnChecked; /// Gets or sets whether to only allow a single row to be toggled at once (Radio button). public bool UseRadioButtons { get; set; } /// Gets the that this instance is wrapping. public ITableSource Wrapping { get; } /// public object this [int row, int col] { get { if (col == 0) { if (UseRadioButtons) { return IsChecked (row) ? RadioCheckedRune : RadioUnCheckedRune; } return IsChecked (row) ? CheckedRune : UnCheckedRune; } return Wrapping [row, col - 1]; } } /// public int Rows => Wrapping.Rows; /// public int Columns => Wrapping.Columns + 1; /// public string [] ColumnNames { get { List toReturn = Wrapping.ColumnNames.ToList (); toReturn.Insert (0, " "); return toReturn.ToArray (); } } /// Clears the toggled state of all rows. protected abstract void ClearAllToggles (); /// Returns true if is checked. /// /// protected abstract bool IsChecked (int row); /// /// Called when the 'toggled all' action is performed. This should change state from 'some selected' to 'all /// selected' or clear selection if all area already selected. /// protected abstract void ToggleAllRows (); /// Flips the checked state of the given / /// protected abstract void ToggleRow (int row); /// /// Flips the checked state for a collection of rows. If some (but not all) are selected they should flip to all /// selected. /// /// protected abstract void ToggleRows (int [] range); private void TableView_CellToggled (object sender, CellToggledEventArgs e) { // Suppress default toggle behavior when using checkboxes // and instead handle ourselves int [] range = tableView.GetAllSelectedCells ().Select (c => c.Y).Distinct ().ToArray (); if (UseRadioButtons) { // multi selection makes it unclear what to toggle in this situation if (range.Length != 1) { e.Cancel = true; return; } ClearAllToggles (); ToggleRow (range.Single ()); } else { ToggleRows (range); } e.Cancel = true; tableView.SetNeedsDraw (); } private void TableView_MouseClick (object sender, MouseEventArgs e) { // we only care about clicks (not movements) if (!e.Flags.HasFlag (MouseFlags.Button1Clicked)) { return; } Point? hit = tableView.ScreenToCell (e.Position.X, e.Position.Y, out int? headerIfAny); if (headerIfAny.HasValue && headerIfAny.Value == 0) { // clicking in header with radio buttons does nothing if (UseRadioButtons) { return; } // otherwise it ticks all rows ToggleAllRows (); e.Handled = true; tableView.SetNeedsDraw (); } else if (hit.HasValue && hit.Value.X == 0) { if (UseRadioButtons) { ClearAllToggles (); ToggleRow (hit.Value.Y); } else { ToggleRow (hit.Value.Y); } e.Handled = true; tableView.SetNeedsDraw (); } } }