namespace Terminal.Gui;
/// An with expandable rows.
///
public class TreeTableSource : IEnumerableTableSource, IDisposable where T : class
{
private readonly Dictionary> _lambdas;
private readonly TableView _tableView;
private readonly TreeView _tree;
///
/// Creates a new instance of presenting the given . This
/// source should only be used with .
///
/// The table this source will provide data for.
///
/// Column name to use for the first column of the table (where the tree branches/leaves will
/// be rendered.
///
///
/// The tree data to render. This should be a new view and not used elsewhere (e.g. via
/// ).
///
///
/// Getter methods for each additional property you want to present in the table. For example:
///
/// new () {
/// { "Colname1", (t)=>t.SomeField},
/// { "Colname2", (t)=>t.SomeOtherField}
/// }
///
///
public TreeTableSource (
TableView table,
string firstColumnName,
TreeView tree,
Dictionary> subsequentColumns
)
{
_tableView = table;
_tree = tree;
_tableView.KeyDown += Table_KeyPress;
_tableView.MouseClick += Table_MouseClick;
List colList = subsequentColumns.Keys.ToList ();
colList.Insert (0, firstColumnName);
ColumnNames = colList.ToArray ();
_lambdas = subsequentColumns;
}
///
public void Dispose ()
{
_tableView.KeyDown -= Table_KeyPress;
_tableView.MouseClick -= Table_MouseClick;
_tree.Dispose ();
}
///
public object this [int row, int col] =>
col == 0 ? GetColumnZeroRepresentationFromTree (row) : _lambdas [ColumnNames [col]] (RowToObject (row));
///
public int Rows => _tree.BuildLineMap ().Count;
///
public int Columns => _lambdas.Count + 1;
///
public string [] ColumnNames { get; }
///
public T GetObjectOnRow (int row) { return RowToObject (row); }
///
public IEnumerable GetAllObjects () { return _tree.BuildLineMap ().Select (b => b.Model); }
/// Returns the tree model object rendering on the given of the table.
/// Row in table.
///
public T RowToObject (int row) { return _tree.BuildLineMap ().ElementAt (row).Model; }
private string GetColumnZeroRepresentationFromTree (int row)
{
Branch branch = RowToBranch (row);
// Everything on line before the expansion run and branch text
Rune [] prefix = branch.GetLinePrefix (Application.Driver).ToArray ();
Rune expansion = branch.GetExpandableSymbol (Application.Driver);
string lineBody = _tree.AspectGetter (branch.Model) ?? "";
var sb = new StringBuilder ();
foreach (Rune p in prefix)
{
sb.Append (p);
}
sb.Append (expansion);
sb.Append (lineBody);
return sb.ToString ();
}
private bool IsInTreeColumn (int column, bool isKeyboard)
{
string [] colNames = _tableView.Table.ColumnNames;
if (column < 0 || column >= colNames.Length)
{
return false;
}
// if full row is selected then it is hard to tell which sub cell in the tree
// has focus so we should typically just always respond with expand/collapse
if (_tableView.FullRowSelect && isKeyboard)
{
return true;
}
// we cannot just check that SelectedColumn is 0 because source may
// be wrapped e.g. with a CheckBoxTableSourceWrapperBase
return colNames [column] == ColumnNames [0];
}
private Branch RowToBranch (int row) { return _tree.BuildLineMap ().ElementAt (row); }
private void Table_KeyPress (object sender, Key e)
{
if (!IsInTreeColumn (_tableView.SelectedColumn, true))
{
return;
}
T obj = _tree.GetObjectOnRow (_tableView.SelectedRow);
if (obj is null)
{
return;
}
if (e.KeyCode == KeyCode.CursorLeft)
{
if (_tree.IsExpanded (obj))
{
_tree.Collapse (obj);
e.Handled = true;
}
}
if (e.KeyCode == KeyCode.CursorRight)
{
if (_tree.CanExpand (obj) && !_tree.IsExpanded (obj))
{
_tree.Expand (obj);
e.Handled = true;
}
}
if (e.Handled)
{
_tree.InvalidateLineMap ();
_tableView.SetNeedsDisplay ();
}
}
private void Table_MouseClick (object sender, MouseEventEventArgs e)
{
Point? hit = _tableView.ScreenToCell (e.MouseEvent.Position.X, e.MouseEvent.Position.Y, out int? headerIfAny, out int? offsetX);
if (hit is null || headerIfAny is { } || !IsInTreeColumn (hit.Value.X, false) || offsetX is null)
{
return;
}
Branch branch = RowToBranch (hit.Value.Y);
if (branch.IsHitOnExpandableSymbol (Application.Driver, offsetX.Value))
{
T m = branch.Model;
if (_tree.CanExpand (m) && !_tree.IsExpanded (m))
{
_tree.Expand (m);
e.Handled = true;
}
else if (_tree.IsExpanded (m))
{
_tree.Collapse (m);
e.Handled = true;
}
}
if (e.Handled)
{
_tree.InvalidateLineMap ();
_tableView.SetNeedsDisplay ();
}
}
}