using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Text;
namespace Terminal.Gui;
///
/// An with expandable rows.
///
///
public class TreeTableSource : IEnumerableTableSource, IDisposable where T : class {
private TreeView _tree;
private string [] _cols;
private Dictionary> _lamdas;
private TableView _tableView;
///
/// 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;
var colList = subsequentColumns.Keys.ToList ();
colList.Insert (0, firstColumnName);
_cols = colList.ToArray ();
_lamdas = subsequentColumns;
}
///
public object this [int row, int col] =>
col == 0 ? GetColumnZeroRepresentationFromTree (row) :
_lamdas [ColumnNames [col]] (RowToObject (row));
///
public int Rows => _tree.BuildLineMap ().Count;
///
public int Columns => _lamdas.Count + 1;
///
public string [] ColumnNames => _cols;
///
public void Dispose ()
{
_tableView.KeyDown -= Table_KeyPress;
_tableView.MouseClick -= Table_MouseClick;
_tree.Dispose ();
}
///
/// 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)
{
var 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 (var p in prefix) {
sb.Append (p);
}
sb.Append (expansion);
sb.Append (lineBody);
return sb.ToString ();
}
private void Table_KeyPress (object sender, Key e)
{
if (!IsInTreeColumn (_tableView.SelectedColumn, true)) {
return;
}
var obj = _tree.GetObjectOnRow (_tableView.SelectedRow);
if (obj == 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)
{
var hit = _tableView.ScreenToCell (e.MouseEvent.X, e.MouseEvent.Y, out var headerIfAny, out var offsetX);
if (hit == null || headerIfAny != null || !IsInTreeColumn (hit.Value.X, false) || offsetX == null) {
return;
}
var branch = RowToBranch (hit.Value.Y);
if (branch.IsHitOnExpandableSymbol (Application.Driver, offsetX.Value)) {
var 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 ();
}
}
private Branch RowToBranch (int row)
{
return _tree.BuildLineMap ().ElementAt (row);
}
private bool IsInTreeColumn (int column, bool isKeyboard)
{
var 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] == _cols [0];
}
///
public T GetObjectOnRow (int row)
{
return RowToObject (row);
}
///
public IEnumerable GetAllObjects ()
{
return _tree.BuildLineMap ().Select (b => b.Model);
}
}