123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192 |
- namespace Terminal.Gui;
- /// <summary>
- /// <see cref="ITableSource"/> for a <see cref="TableView"/> which adds a checkbox column as an additional column
- /// in the table.
- /// </summary>
- /// <remarks>
- /// This class wraps another <see cref="ITableSource"/> and dynamically serves its rows/cols plus an extra column.
- /// Data in the wrapped source can be dynamic (change over time).
- /// </remarks>
- public abstract class CheckBoxTableSourceWrapperBase : ITableSource
- {
- private readonly TableView tableView;
- /// <summary>
- /// Creates a new instance of the class presenting the data in <paramref name="toWrap"/> plus an additional
- /// checkbox column.
- /// </summary>
- /// <param name="tableView">
- /// The <see cref="TableView"/> this source will be used with. This is required for event
- /// registration.
- /// </param>
- /// <param name="toWrap">The original data source of the <see cref="TableView"/> that you want to add checkboxes to.</param>
- 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;
- }
- /// <summary>
- /// Gets or sets the character to use for checked entries. Defaults to <see cref="Glyphs.CheckStateChecked"/>
- /// </summary>
- public Rune CheckedRune { get; set; } = Glyphs.CheckStateChecked;
- /// <summary>
- /// Gets or sets the character to use for checked entry when <see cref="UseRadioButtons"/> is true. Defaults to
- /// <see cref="Glyphs.Selected"/>
- /// </summary>
- public Rune RadioCheckedRune { get; set; } = Glyphs.Selected;
- /// <summary>
- /// Gets or sets the character to use for unchecked entries when <see cref="UseRadioButtons"/> is true. Defaults
- /// to <see cref="Glyphs.UnSelected"/>
- /// </summary>
- public Rune RadioUnCheckedRune { get; set; } = Glyphs.UnSelected;
- /// <summary>
- /// Gets or sets the character to use for UnChecked entries. Defaults to <see cref="Glyphs.CheckStateUnChecked"/>
- /// </summary>
- public Rune UnCheckedRune { get; set; } = Glyphs.CheckStateUnChecked;
- /// <summary>Gets or sets whether to only allow a single row to be toggled at once (Radio button).</summary>
- public bool UseRadioButtons { get; set; }
- /// <summary>Gets the <see cref="ITableSource"/> that this instance is wrapping.</summary>
- public ITableSource Wrapping { get; }
- /// <inheritdoc/>
- 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];
- }
- }
- /// <inheritdoc/>
- public int Rows => Wrapping.Rows;
- /// <inheritdoc/>
- public int Columns => Wrapping.Columns + 1;
- /// <inheritdoc/>
- public string [] ColumnNames
- {
- get
- {
- List<string> toReturn = Wrapping.ColumnNames.ToList ();
- toReturn.Insert (0, " ");
- return toReturn.ToArray ();
- }
- }
- /// <summary>Clears the toggled state of all rows.</summary>
- protected abstract void ClearAllToggles ();
- /// <summary>Returns true if <paramref name="row"/> is checked.</summary>
- /// <param name="row"></param>
- /// <returns></returns>
- protected abstract bool IsChecked (int row);
- /// <summary>
- /// 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.
- /// </summary>
- protected abstract void ToggleAllRows ();
- /// <summary>Flips the checked state of the given <paramref name="row"/>/</summary>
- /// <param name="row"></param>
- protected abstract void ToggleRow (int row);
- /// <summary>
- /// Flips the checked state for a collection of rows. If some (but not all) are selected they should flip to all
- /// selected.
- /// </summary>
- /// <param name="range"></param>
- 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 ();
- }
- }
- }
|