|
@@ -6,6 +6,9 @@ using System.Linq;
|
|
|
|
|
|
namespace Terminal.Gui {
|
|
namespace Terminal.Gui {
|
|
|
|
|
|
|
|
+ /// <summary>
|
|
|
|
+ /// Hierarchical tree view with expandable branches. Branch objects are dynamically determined when expanded using a user defined <see cref="ChildrenGetterDelegate"/>
|
|
|
|
+ /// </summary>
|
|
public class TreeView : View
|
|
public class TreeView : View
|
|
{
|
|
{
|
|
/// <summary>
|
|
/// <summary>
|
|
@@ -43,7 +46,10 @@ namespace Terminal.Gui {
|
|
/// </summary>
|
|
/// </summary>
|
|
public int ScrollOffset {get; private set;}
|
|
public int ScrollOffset {get; private set;}
|
|
|
|
|
|
- public TreeView ()
|
|
|
|
|
|
+ /// <summary>
|
|
|
|
+ /// Creates a new tree view with absolute positioning. Use <see cref="AddObjects(IEnumerable{object})"/> to set set root objects for the tree
|
|
|
|
+ /// </summary>
|
|
|
|
+ public TreeView ():base()
|
|
{
|
|
{
|
|
CanFocus = true;
|
|
CanFocus = true;
|
|
}
|
|
}
|
|
@@ -59,11 +65,37 @@ namespace Terminal.Gui {
|
|
SetNeedsDisplay();
|
|
SetNeedsDisplay();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
+
|
|
|
|
+ /// <summary>
|
|
|
|
+ /// Removes all objects from the tree and clears <see cref="SelectedObject"/>
|
|
|
|
+ /// </summary>
|
|
|
|
+ public void ClearObjects()
|
|
|
|
+ {
|
|
|
|
+ SelectedObject = null;
|
|
|
|
+ roots = new Dictionary<object, Branch>();
|
|
|
|
+ SetNeedsDisplay();
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /// <summary>
|
|
|
|
+ /// Removes the given root object from the tree
|
|
|
|
+ /// </summary>
|
|
|
|
+ /// <remarks>If <paramref name="o"/> is the currently <see cref="SelectedObject"/> then the selection is cleared</remarks>
|
|
|
|
+ /// <param name="o"></param>
|
|
|
|
+ public void Remove(object o)
|
|
|
|
+ {
|
|
|
|
+ if(roots.ContainsKey(o)) {
|
|
|
|
+ roots.Remove(o);
|
|
|
|
+ SetNeedsDisplay();
|
|
|
|
+
|
|
|
|
+ if(Equals(SelectedObject,o))
|
|
|
|
+ SelectedObject = null;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
|
|
/// <summary>
|
|
/// <summary>
|
|
/// Adds many new root level objects. Objects that are already root objects are ignored
|
|
/// Adds many new root level objects. Objects that are already root objects are ignored
|
|
/// </summary>
|
|
/// </summary>
|
|
- /// <param name="o"></param>
|
|
|
|
|
|
+ /// <param name="collection">Objects to add as new root level objects</param>
|
|
public void AddObjects(IEnumerable<object> collection)
|
|
public void AddObjects(IEnumerable<object> collection)
|
|
{
|
|
{
|
|
bool objectsAdded = false;
|
|
bool objectsAdded = false;
|
|
@@ -83,7 +115,7 @@ namespace Terminal.Gui {
|
|
/// Returns the string representation of model objects hosted in the tree. Default implementation is to call <see cref="object.ToString"/>
|
|
/// Returns the string representation of model objects hosted in the tree. Default implementation is to call <see cref="object.ToString"/>
|
|
/// </summary>
|
|
/// </summary>
|
|
/// <value></value>
|
|
/// <value></value>
|
|
- public Func<object,string> AspectGetter {get;set;} = (o)=>o.ToString();
|
|
|
|
|
|
+ public AspectGetterDelegate AspectGetter {get;set;} = (o)=>o.ToString();
|
|
|
|
|
|
///<inheritdoc/>
|
|
///<inheritdoc/>
|
|
public override void Redraw (Rect bounds)
|
|
public override void Redraw (Rect bounds)
|
|
@@ -142,8 +174,19 @@ namespace Terminal.Gui {
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+ /// <summary>
|
|
|
|
+ /// Symbol to use for expanded branch nodes to indicate to the user that they can be collapsed. Defaults to '-'
|
|
|
|
+ /// </summary>
|
|
public char ExpandedSymbol {get;set;} = '-';
|
|
public char ExpandedSymbol {get;set;} = '-';
|
|
|
|
+
|
|
|
|
+ /// <summary>
|
|
|
|
+ /// Symbol to use for branch nodes that can be expanded to indicate this to the user. Defaults to '+'
|
|
|
|
+ /// </summary>
|
|
public char ExpandableSymbol {get;set;} = '+';
|
|
public char ExpandableSymbol {get;set;} = '+';
|
|
|
|
+
|
|
|
|
+ /// <summary>
|
|
|
|
+ /// Symbol to use for branch nodes that cannot be expanded (as they have no children). Defaults to space ' '
|
|
|
|
+ /// </summary>
|
|
public char LeafSymbol {get;set;} = ' ';
|
|
public char LeafSymbol {get;set;} = ' ';
|
|
|
|
|
|
/// <inheritdoc/>
|
|
/// <inheritdoc/>
|
|
@@ -163,12 +206,48 @@ namespace Terminal.Gui {
|
|
case Key.CursorDown:
|
|
case Key.CursorDown:
|
|
AdjustSelection(1);
|
|
AdjustSelection(1);
|
|
break;
|
|
break;
|
|
|
|
+ case Key.PageUp:
|
|
|
|
+ AdjustSelection(-Bounds.Height);
|
|
|
|
+ break;
|
|
|
|
+
|
|
|
|
+ case Key.PageDown:
|
|
|
|
+ AdjustSelection(Bounds.Height);
|
|
|
|
+ break;
|
|
|
|
+ case Key.Home:
|
|
|
|
+ GoToFirst();
|
|
|
|
+ break;
|
|
|
|
+ case Key.End:
|
|
|
|
+ GoToEnd();
|
|
|
|
+ break;
|
|
}
|
|
}
|
|
|
|
|
|
PositionCursor ();
|
|
PositionCursor ();
|
|
return true;
|
|
return true;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+ /// <summary>
|
|
|
|
+ /// Changes the <see cref="SelectedObject"/> to the first root object and resets the <see cref="ScrollOffset"/> to 0
|
|
|
|
+ /// </summary>
|
|
|
|
+ public void GoToFirst()
|
|
|
|
+ {
|
|
|
|
+ ScrollOffset = 0;
|
|
|
|
+ SelectedObject = roots.Keys.FirstOrDefault();
|
|
|
|
+
|
|
|
|
+ SetNeedsDisplay();
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /// <summary>
|
|
|
|
+ /// Changes the <see cref="SelectedObject"/> to the last object in the tree and scrolls so that it is visible
|
|
|
|
+ /// </summary>
|
|
|
|
+ public void GoToEnd ()
|
|
|
|
+ {
|
|
|
|
+ var map = BuildLineMap();
|
|
|
|
+ ScrollOffset = Math.Max(0,map.Length - Bounds.Height +1);
|
|
|
|
+ SelectedObject = map.Last().Model;
|
|
|
|
+
|
|
|
|
+ SetNeedsDisplay();
|
|
|
|
+ }
|
|
|
|
+
|
|
/// <summary>
|
|
/// <summary>
|
|
/// Changes the selected object by a number of screen lines
|
|
/// Changes the selected object by a number of screen lines
|
|
/// </summary>
|
|
/// </summary>
|
|
@@ -201,7 +280,7 @@ namespace Terminal.Gui {
|
|
else if(newIdx >= ScrollOffset + Bounds.Height){
|
|
else if(newIdx >= ScrollOffset + Bounds.Height){
|
|
|
|
|
|
//if user has scrolled off bottom of visible tree
|
|
//if user has scrolled off bottom of visible tree
|
|
- ScrollOffset = Math.Max(0,newIdx - Bounds.Height);
|
|
|
|
|
|
+ ScrollOffset = Math.Max(0,(newIdx+1) - Bounds.Height);
|
|
|
|
|
|
}
|
|
}
|
|
}
|
|
}
|
|
@@ -211,21 +290,29 @@ namespace Terminal.Gui {
|
|
SetNeedsDisplay();
|
|
SetNeedsDisplay();
|
|
}
|
|
}
|
|
|
|
|
|
- private void Expand(object selectedObject)
|
|
|
|
|
|
+ /// <summary>
|
|
|
|
+ /// Expands the supplied object if it is contained in the tree (either as a root object or as an exposed branch object)
|
|
|
|
+ /// </summary>
|
|
|
|
+ /// <param name="toExpand">The object to expand</param>
|
|
|
|
+ public void Expand(object toExpand)
|
|
{
|
|
{
|
|
- if(selectedObject == null)
|
|
|
|
- return;
|
|
|
|
-
|
|
|
|
- ObjectToBranch(selectedObject).IsExpanded = true;
|
|
|
|
|
|
+ if(toExpand == null)
|
|
|
|
+ return;
|
|
|
|
+
|
|
|
|
+ ObjectToBranch(toExpand)?.Expand();
|
|
SetNeedsDisplay();
|
|
SetNeedsDisplay();
|
|
}
|
|
}
|
|
|
|
|
|
- private void Collapse(object selectedObject)
|
|
|
|
|
|
+ /// <summary>
|
|
|
|
+ /// Collapses the supplied object if it is currently expanded
|
|
|
|
+ /// </summary>
|
|
|
|
+ /// <param name="toCollapse">The object to collapse</param>
|
|
|
|
+ public void Collapse(object toCollapse)
|
|
{
|
|
{
|
|
- if(selectedObject == null)
|
|
|
|
- return;
|
|
|
|
|
|
+ if(toCollapse == null)
|
|
|
|
+ return;
|
|
|
|
|
|
- ObjectToBranch(selectedObject).IsExpanded = false;
|
|
|
|
|
|
+ ObjectToBranch(toCollapse)?.Collapse();
|
|
SetNeedsDisplay();
|
|
SetNeedsDisplay();
|
|
}
|
|
}
|
|
|
|
|
|
@@ -233,7 +320,7 @@ namespace Terminal.Gui {
|
|
/// Returns the corresponding <see cref="Branch"/> in the tree for <paramref name="toFind"/>. This will not work for objects hidden by their parent being collapsed
|
|
/// Returns the corresponding <see cref="Branch"/> in the tree for <paramref name="toFind"/>. This will not work for objects hidden by their parent being collapsed
|
|
/// </summary>
|
|
/// </summary>
|
|
/// <param name="toFind"></param>
|
|
/// <param name="toFind"></param>
|
|
- /// <returns></returns>
|
|
|
|
|
|
+ /// <returns>The branch for <paramref name="toFind"/> or null if it is not currently exposed in the tree</returns>
|
|
private Branch ObjectToBranch(object toFind)
|
|
private Branch ObjectToBranch(object toFind)
|
|
{
|
|
{
|
|
return BuildLineMap().FirstOrDefault(o=>o.Model.Equals(toFind));
|
|
return BuildLineMap().FirstOrDefault(o=>o.Model.Equals(toFind));
|
|
@@ -242,15 +329,36 @@ namespace Terminal.Gui {
|
|
|
|
|
|
class Branch
|
|
class Branch
|
|
{
|
|
{
|
|
|
|
+ /// <summary>
|
|
|
|
+ /// True if the branch is expanded to reveal child branches
|
|
|
|
+ /// </summary>
|
|
public bool IsExpanded {get;set;}
|
|
public bool IsExpanded {get;set;}
|
|
- public Object Model{get;set;}
|
|
|
|
|
|
+
|
|
|
|
+ /// <summary>
|
|
|
|
+ /// The users object that is being displayed by this branch of the tree
|
|
|
|
+ /// </summary>
|
|
|
|
+ public object Model {get;set;}
|
|
|
|
|
|
|
|
+ /// <summary>
|
|
|
|
+ /// The depth of the current branch. Depth of 0 indicates root level branches
|
|
|
|
+ /// </summary>
|
|
public int Depth {get;set;} = 0;
|
|
public int Depth {get;set;} = 0;
|
|
|
|
+
|
|
|
|
+ /// <summary>
|
|
|
|
+ /// The children of the current branch. This is null until the first call to <see cref="FetchChildren"/> to avoid enumerating the entire underlying hierarchy
|
|
|
|
+ /// </summary>
|
|
public Dictionary<object,Branch> ChildBranches {get;set;}
|
|
public Dictionary<object,Branch> ChildBranches {get;set;}
|
|
|
|
|
|
|
|
+
|
|
private TreeView tree;
|
|
private TreeView tree;
|
|
|
|
|
|
- public Branch(TreeView tree,Branch parentBranchIfAny,Object model)
|
|
|
|
|
|
+ /// <summary>
|
|
|
|
+ /// Declares a new branch of <paramref name="tree"/> in which the users object <paramref name="model"/> is presented
|
|
|
|
+ /// </summary>
|
|
|
|
+ /// <param name="tree">The UI control in which the branch resides</param>
|
|
|
|
+ /// <param name="parentBranchIfAny">Pass null for root level branches, otherwise pass the parent</param>
|
|
|
|
+ /// <param name="model">The user's object that should be displayed</param>
|
|
|
|
+ public Branch(TreeView tree,Branch parentBranchIfAny,object model)
|
|
{
|
|
{
|
|
this.tree = tree;
|
|
this.tree = tree;
|
|
this.Model = model;
|
|
this.Model = model;
|
|
@@ -292,7 +400,11 @@ namespace Terminal.Gui {
|
|
driver.AddStr(representation.PadRight(availableWidth));
|
|
driver.AddStr(representation.PadRight(availableWidth));
|
|
}
|
|
}
|
|
|
|
|
|
- char GetExpandableIcon()
|
|
|
|
|
|
+ /// <summary>
|
|
|
|
+ /// Returns an appropriate symbol for displaying next to the string representation of the <see cref="Model"/> object to indicate whether it <see cref="IsExpanded"/> or not (or it is a leaf)
|
|
|
|
+ /// </summary>
|
|
|
|
+ /// <returns></returns>
|
|
|
|
+ public char GetExpandableIcon()
|
|
{
|
|
{
|
|
if(IsExpanded)
|
|
if(IsExpanded)
|
|
return tree.ExpandedSymbol;
|
|
return tree.ExpandedSymbol;
|
|
@@ -302,6 +414,25 @@ namespace Terminal.Gui {
|
|
|
|
|
|
return ChildBranches.Any() ? tree.ExpandableSymbol : tree.LeafSymbol;
|
|
return ChildBranches.Any() ? tree.ExpandableSymbol : tree.LeafSymbol;
|
|
}
|
|
}
|
|
|
|
+
|
|
|
|
+ /// <summary>
|
|
|
|
+ /// Expands the current branch if possible
|
|
|
|
+ /// </summary>
|
|
|
|
+ public void Expand()
|
|
|
|
+ {
|
|
|
|
+ if(ChildBranches == null) {
|
|
|
|
+ FetchChildren();
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if (ChildBranches.Any ()) {
|
|
|
|
+ IsExpanded = true;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ internal void Collapse ()
|
|
|
|
+ {
|
|
|
|
+ IsExpanded = false;
|
|
|
|
+ }
|
|
}
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// <summary>
|
|
@@ -310,4 +441,11 @@ namespace Terminal.Gui {
|
|
/// <param name="model">The parent whose children should be fetched</param>
|
|
/// <param name="model">The parent whose children should be fetched</param>
|
|
/// <returns>An enumerable over the children</returns>
|
|
/// <returns>An enumerable over the children</returns>
|
|
public delegate IEnumerable<object> ChildrenGetterDelegate(object model);
|
|
public delegate IEnumerable<object> ChildrenGetterDelegate(object model);
|
|
|
|
+
|
|
|
|
+ /// <summary>
|
|
|
|
+ /// Delegates of this type are used to fetch string representations of user's model objects
|
|
|
|
+ /// </summary>
|
|
|
|
+ /// <param name="model"></param>
|
|
|
|
+ /// <returns></returns>
|
|
|
|
+ public delegate string AspectGetterDelegate(object model);
|
|
}
|
|
}
|