mirror of
https://github.com/gui-cs/Terminal.Gui.git
synced 2025-12-26 15:57:56 +01:00
* Add ITreeViewFilter * Fix for v2 * Fix for new private variable name
This commit is contained in:
14
Terminal.Gui/Views/ITreeViewFilter.cs
Normal file
14
Terminal.Gui/Views/ITreeViewFilter.cs
Normal file
@@ -0,0 +1,14 @@
|
||||
namespace Terminal.Gui {
|
||||
|
||||
/// <summary>
|
||||
/// Provides filtering for a <see cref="TreeView"/>.
|
||||
/// </summary>
|
||||
public interface ITreeViewFilter<T> where T : class {
|
||||
|
||||
/// <summary>
|
||||
/// Return <see langword="true"/> if the <paramref name="model"/> should
|
||||
/// be included in the tree.
|
||||
/// </summary>
|
||||
bool IsMatch (T model);
|
||||
}
|
||||
}
|
||||
@@ -214,6 +214,13 @@ namespace Terminal.Gui {
|
||||
|
||||
CursorVisibility desiredCursorVisibility = CursorVisibility.Invisible;
|
||||
|
||||
/// <summary>
|
||||
/// Interface for filtering which lines of the tree are displayed
|
||||
/// e.g. to provide text searching. Defaults to <see langword="null"/>
|
||||
/// (no filtering).
|
||||
/// </summary>
|
||||
public ITreeViewFilter<T> Filter = null;
|
||||
|
||||
/// <summary>
|
||||
/// Get / Set the wished cursor when the tree is focused.
|
||||
/// Only applies when <see cref="MultiSelect"/> is true.
|
||||
@@ -545,7 +552,12 @@ namespace Terminal.Gui {
|
||||
List<Branch<T>> toReturn = new List<Branch<T>> ();
|
||||
|
||||
foreach (var root in roots.Values) {
|
||||
toReturn.AddRange (AddToLineMap (root));
|
||||
|
||||
var toAdd = AddToLineMap (root, false, out var isMatch);
|
||||
if(isMatch)
|
||||
{
|
||||
toReturn.AddRange (toAdd);
|
||||
}
|
||||
}
|
||||
|
||||
cachedLineMap = new ReadOnlyCollection<Branch<T>> (toReturn);
|
||||
@@ -555,17 +567,44 @@ namespace Terminal.Gui {
|
||||
return cachedLineMap;
|
||||
}
|
||||
|
||||
private IEnumerable<Branch<T>> AddToLineMap (Branch<T> currentBranch)
|
||||
private bool IsFilterMatch (Branch<T> branch)
|
||||
{
|
||||
yield return currentBranch;
|
||||
return Filter?.IsMatch(branch.Model) ?? true;
|
||||
}
|
||||
|
||||
private IEnumerable<Branch<T>> AddToLineMap (Branch<T> currentBranch,bool parentMatches, out bool match)
|
||||
{
|
||||
bool weMatch = IsFilterMatch(currentBranch);
|
||||
bool anyChildMatches = false;
|
||||
|
||||
var toReturn = new List<Branch<T>>();
|
||||
var children = new List<Branch<T>>();
|
||||
|
||||
if (currentBranch.IsExpanded) {
|
||||
foreach (var subBranch in currentBranch.ChildBranches.Values) {
|
||||
foreach (var sub in AddToLineMap (subBranch)) {
|
||||
yield return sub;
|
||||
|
||||
foreach (var sub in AddToLineMap (subBranch, weMatch, out var childMatch)) {
|
||||
|
||||
if(childMatch)
|
||||
{
|
||||
children.Add(sub);
|
||||
anyChildMatches = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(parentMatches || weMatch || anyChildMatches)
|
||||
{
|
||||
match = true;
|
||||
toReturn.Add(currentBranch);
|
||||
}
|
||||
else{
|
||||
match = false;
|
||||
}
|
||||
|
||||
toReturn.AddRange(children);
|
||||
return toReturn;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -1290,9 +1329,9 @@ namespace Terminal.Gui {
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clears any cached results of <see cref="BuildLineMap"/>
|
||||
/// Clears any cached results of the tree state.
|
||||
/// </summary>
|
||||
protected void InvalidateLineMap ()
|
||||
public void InvalidateLineMap ()
|
||||
{
|
||||
cachedLineMap = null;
|
||||
}
|
||||
|
||||
65
Terminal.Gui/Views/TreeViewTextFilter.cs
Normal file
65
Terminal.Gui/Views/TreeViewTextFilter.cs
Normal file
@@ -0,0 +1,65 @@
|
||||
using System;
|
||||
|
||||
namespace Terminal.Gui {
|
||||
|
||||
/// <summary>
|
||||
/// <see cref="ITreeViewFilter{T}"/> implementation which searches the
|
||||
/// <see cref="TreeView{T}.AspectGetter"/> of the model for the given
|
||||
/// <see cref="Text"/>.
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
public class TreeViewTextFilter<T> : ITreeViewFilter<T> where T : class {
|
||||
readonly TreeView<T> _forTree;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of the filter for use with <paramref name="forTree"/>.
|
||||
/// Set <see cref="Text"/> to begin filtering.
|
||||
/// </summary>
|
||||
/// <param name="forTree"></param>
|
||||
/// <exception cref="ArgumentNullException"></exception>
|
||||
public TreeViewTextFilter (TreeView<T> forTree)
|
||||
{
|
||||
_forTree = forTree ?? throw new ArgumentNullException (nameof (forTree));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The case sensitivity of the search match.
|
||||
/// Defaults to <see cref="StringComparison.OrdinalIgnoreCase"/>.
|
||||
/// </summary>
|
||||
public StringComparison Comparer { get; set; } = StringComparison.OrdinalIgnoreCase;
|
||||
private string text;
|
||||
|
||||
/// <summary>
|
||||
/// The text that will be searched for in the <see cref="TreeView{T}"/>
|
||||
/// </summary>
|
||||
public string Text {
|
||||
get { return text; }
|
||||
set {
|
||||
text = value;
|
||||
RefreshTreeView ();
|
||||
}
|
||||
}
|
||||
|
||||
private void RefreshTreeView ()
|
||||
{
|
||||
_forTree.InvalidateLineMap ();
|
||||
_forTree.SetNeedsDisplay ();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns <typeparamref name="T"/> if there is no <see cref="Text"/> or
|
||||
/// the text matches the <see cref="TreeView{T}.AspectGetter"/> of the
|
||||
/// <paramref name="model"/>.
|
||||
/// </summary>
|
||||
/// <param name="model"></param>
|
||||
/// <returns></returns>
|
||||
public bool IsMatch (T model)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace (Text)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return _forTree.AspectGetter (model)?.IndexOf (Text, Comparer) != -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -83,11 +83,30 @@ namespace UICatalog.Scenarios {
|
||||
|
||||
_treeView = new TreeView<object> () {
|
||||
X = 0,
|
||||
Y = 0,
|
||||
Y = 1,
|
||||
Width = Dim.Percent (50),
|
||||
Height = Dim.Fill (),
|
||||
};
|
||||
|
||||
var lblSearch = new Label("Search");
|
||||
var tfSearch = new TextField(){
|
||||
Width = 20,
|
||||
X = Pos.Right(lblSearch),
|
||||
};
|
||||
|
||||
Win.Add(lblSearch);
|
||||
Win.Add(tfSearch);
|
||||
|
||||
var filter = new TreeViewTextFilter<object>(_treeView);
|
||||
_treeView.Filter = filter;
|
||||
tfSearch.TextChanged += (s,e)=>{
|
||||
filter.Text = tfSearch.Text.ToString();
|
||||
if(_treeView.SelectedObject != null)
|
||||
{
|
||||
_treeView.EnsureVisible(_treeView.SelectedObject);
|
||||
}
|
||||
};
|
||||
|
||||
_treeView.AddObjects (AppDomain.CurrentDomain.GetAssemblies ());
|
||||
_treeView.AspectGetter = GetRepresentation;
|
||||
_treeView.TreeBuilder = new DelegateTreeBuilder<object> (ChildGetter, CanExpand);
|
||||
|
||||
@@ -902,6 +902,75 @@ namespace Terminal.Gui.ViewsTests {
|
||||
new [] { tv.ColorScheme.Normal, pink });
|
||||
}
|
||||
|
||||
[Fact, AutoInitShutdown]
|
||||
public void TestTreeView_Filter ()
|
||||
{
|
||||
var tv = new TreeView { Width = 20, Height = 10 };
|
||||
|
||||
var n1 = new TreeNode ("root one");
|
||||
var n1_1 = new TreeNode ("leaf 1");
|
||||
var n1_2 = new TreeNode ("leaf 2");
|
||||
n1.Children.Add (n1_1);
|
||||
n1.Children.Add (n1_2);
|
||||
|
||||
var n2 = new TreeNode ("root two");
|
||||
tv.AddObject (n1);
|
||||
tv.AddObject (n2);
|
||||
tv.Expand (n1);
|
||||
|
||||
tv.ColorScheme = new ColorScheme ();
|
||||
tv.LayoutSubviews ();
|
||||
tv.Draw ();
|
||||
|
||||
// Normal drawing of the tree view
|
||||
TestHelpers.AssertDriverContentsAre (
|
||||
@"
|
||||
├-root one
|
||||
│ ├─leaf 1
|
||||
│ └─leaf 2
|
||||
└─root two
|
||||
", output);
|
||||
var filter = new TreeViewTextFilter<ITreeNode> (tv);
|
||||
tv.Filter = filter;
|
||||
|
||||
// matches nothing
|
||||
filter.Text = "asdfjhasdf";
|
||||
tv.Draw ();
|
||||
// Normal drawing of the tree view
|
||||
TestHelpers.AssertDriverContentsAre (
|
||||
@"", output);
|
||||
|
||||
|
||||
// Matches everything
|
||||
filter.Text = "root";
|
||||
tv.Draw ();
|
||||
TestHelpers.AssertDriverContentsAre (
|
||||
@"
|
||||
├-root one
|
||||
│ ├─leaf 1
|
||||
│ └─leaf 2
|
||||
└─root two
|
||||
", output);
|
||||
// Matches 2 leaf nodes
|
||||
filter.Text = "leaf";
|
||||
tv.Draw ();
|
||||
TestHelpers.AssertDriverContentsAre (
|
||||
@"
|
||||
├-root one
|
||||
│ ├─leaf 1
|
||||
│ └─leaf 2
|
||||
", output);
|
||||
|
||||
// Matches 1 leaf nodes
|
||||
filter.Text = "leaf 1";
|
||||
tv.Draw ();
|
||||
TestHelpers.AssertDriverContentsAre (
|
||||
@"
|
||||
├-root one
|
||||
│ ├─leaf 1
|
||||
", output);
|
||||
}
|
||||
|
||||
[Fact, AutoInitShutdown]
|
||||
public void DesiredCursorVisibility_MultiSelect ()
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user