From 8d02ecbd78acafb102b0895bf6724a8d4daa6141 Mon Sep 17 00:00:00 2001 From: Ross Ferguson Date: Sat, 9 May 2020 07:00:14 +0100 Subject: [PATCH 01/24] Feature/TextFieldAutoComplete --- Example/demo.cs | 15 +++ Terminal.Gui/Views/TextFieldAutoComplete.cs | 137 ++++++++++++++++++++ 2 files changed, 152 insertions(+) create mode 100644 Terminal.Gui/Views/TextFieldAutoComplete.cs diff --git a/Example/demo.cs b/Example/demo.cs index ab05806b3..3475c48ed 100644 --- a/Example/demo.cs +++ b/Example/demo.cs @@ -1,5 +1,7 @@ using Terminal.Gui; using System; +using System.Linq; +using System.IO; using Mono.Terminal; using System.Collections.Generic; using System.Diagnostics; @@ -410,6 +412,18 @@ static class Demo { } MessageBox.Query (60, 10, "Selected Animals", result == "" ? "No animals selected" : result, "Ok"); } + + static void TextFieldAutoCompleteDemo () + { + var items = Directory.GetFiles (@"..\..\..\Terminal.Gui", "*.cs", SearchOption.AllDirectories).Select (x => Path.GetFileName (x)).ToList (); + var list = new TextFieldAutoComplete (0, 0, 36, 7, items); + list.Changed += (object sender, ustring text) => { Application.RequestStop (); }; + + var d = new Dialog ("Select source file", 40, 12) { list }; + Application.Run (d); + + MessageBox.Query (60, 10, "Selected file", list.Text == null ? "Nothing selected" : list.Text, "Ok"); + } #endregion @@ -515,6 +529,7 @@ static class Demo { new MenuBarItem ("_List Demos", new MenuItem [] { new MenuItem ("Select _Multiple Items", "", () => ListSelectionDemo (true)), new MenuItem ("Select _Single Item", "", () => ListSelectionDemo (false)), + new MenuItem ("Search Single Item", "", TextFieldAutoCompleteDemo) }), new MenuBarItem ("A_ssorted", new MenuItem [] { new MenuItem ("_Show text alignments", "", () => ShowTextAlignments ()), diff --git a/Terminal.Gui/Views/TextFieldAutoComplete.cs b/Terminal.Gui/Views/TextFieldAutoComplete.cs new file mode 100644 index 000000000..3c8c1f210 --- /dev/null +++ b/Terminal.Gui/Views/TextFieldAutoComplete.cs @@ -0,0 +1,137 @@ +// +// TextFieldAutoComplete.cs: TextField with AutoComplete +// +// Author: +// Ross Ferguson (ross.c.ferguson@btinternet.com) +// + +using System; +using System.Linq; +using System.Collections.Generic; +using NStack; + +namespace Terminal.Gui { + /// + /// TextField with AutoComplete + /// + public class TextFieldAutoComplete : View { + public event EventHandler Changed; + + readonly IList listsource; + IList searchset; + readonly TextField search; + readonly ListView listview; + readonly int height; + + /// + /// Public constructor + /// + /// The x coordinate + /// The y coordinate + /// The width + /// The height + /// Auto completetion source + public TextFieldAutoComplete(int x, int y, int w, int h, IList source) : base() + { + listsource = searchset = source; + height = h; + search = new TextField(x, y, w, ""); + search.Changed += Search_Changed; + + listview = new ListView(new Rect(x, y + 1, w, Math.Min(height, searchset.Count())), listsource.ToList()) + { + //LayoutStyle = LayoutStyle.Computed, + }; + + this.Add(listview); + this.Add(search); + this.SetFocus(search); + + this.OnEnter += (object sender, EventArgs e) => { this.SetFocus(search); }; + } + + public override bool ProcessKey(KeyEvent e) + { + if (e.Key == Key.Tab) + { + base.ProcessKey(e); + return false; // allow tab-out to next control + } + + if (e.Key == Key.Enter) + { + if (Text == null) + return false; // allow tab-out to next control + + search.Text = Text; + search.CursorPosition = search.Text.Length; + searchset.Clear(); + listview.Clear(); + this.SetFocus(search); + + Changed?.Invoke(this, search.Text); + + return true; + } + + if (e.Key == Key.CursorDown && search.HasFocus) // jump to list + { + this.SetFocus(listview); + listview.SelectedItem = 0; + return true; + } + + if(e.Key == Key.CursorUp && listview.SelectedItem == 0 && listview.HasFocus) // jump back to search + { + this.SetFocus(search); + return true; + } + + // Unix emulation + if (e.Key == Key.ControlU || e.Key == Key.Esc) + { + Reset(); + return true; + } + + return base.ProcessKey(e); + } + + /// + /// The currenlty selected list item + /// + public string Text + { + get + { + if (listview.Source.Count == 0 || searchset.Count() == 0) + return search.Text.ToString(); + + return searchset.ToList()[listview.SelectedItem] as string; + } + } + + /// + /// Reset to full original list + /// + private void Reset() + { + search.Text = ""; + searchset = listsource; + listview.SetSource(searchset.ToList()); + this.SetFocus(search); + } + + private void Search_Changed(object sender, ustring e) + { + + if (string.IsNullOrEmpty(search.Text.ToString())) + searchset = listsource; + else + searchset = listsource.Where(x => x.ToLower().Contains(search.Text.ToString().ToLower())).ToList(); + + listview.SetSource(searchset.ToList()); + listview.Height = Math.Min(height, searchset.Count()); + } + } +} From 26e0478f56e08fb6533f553316019e4747d3ddef Mon Sep 17 00:00:00 2001 From: Ross Ferguson Date: Fri, 15 May 2020 06:04:38 +0100 Subject: [PATCH 02/24] Use LayoutStyle.Computed. Add Text setter --- Example/demo.cs | 2 +- Terminal.Gui/Views/TextFieldAutoComplete.cs | 118 +++++++++++++++----- 2 files changed, 91 insertions(+), 29 deletions(-) diff --git a/Example/demo.cs b/Example/demo.cs index 3475c48ed..c4f910a75 100644 --- a/Example/demo.cs +++ b/Example/demo.cs @@ -422,7 +422,7 @@ static class Demo { var d = new Dialog ("Select source file", 40, 12) { list }; Application.Run (d); - MessageBox.Query (60, 10, "Selected file", list.Text == null ? "Nothing selected" : list.Text, "Ok"); + MessageBox.Query (60, 10, "Selected file", list.Text.ToString() == "" ? "Nothing selected" : list.Text.ToString(), "Ok"); } #endregion diff --git a/Terminal.Gui/Views/TextFieldAutoComplete.cs b/Terminal.Gui/Views/TextFieldAutoComplete.cs index 3c8c1f210..57befd95c 100644 --- a/Terminal.Gui/Views/TextFieldAutoComplete.cs +++ b/Terminal.Gui/Views/TextFieldAutoComplete.cs @@ -4,6 +4,9 @@ // Author: // Ross Ferguson (ross.c.ferguson@btinternet.com) // +// TODO: +// * Completion list auto appears when hosted directly in a Window as opposed to a dialog +// using System; using System.Linq; @@ -15,13 +18,22 @@ namespace Terminal.Gui { /// TextField with AutoComplete /// public class TextFieldAutoComplete : View { + /// + /// Changed event, raised when the selection has been confirmed. + /// + /// + /// Client code can hook up to this event, it is + /// raised when the selection has been confirmed. + /// public event EventHandler Changed; readonly IList listsource; IList searchset; + ustring text = ""; readonly TextField search; readonly ListView listview; readonly int height; + readonly int width; /// /// Public constructor @@ -31,23 +43,34 @@ namespace Terminal.Gui { /// The width /// The height /// Auto completetion source - public TextFieldAutoComplete(int x, int y, int w, int h, IList source) : base() + public TextFieldAutoComplete(int x, int y, int w, int h, IList source) { listsource = searchset = source; height = h; + width = w; search = new TextField(x, y, w, ""); search.Changed += Search_Changed; - listview = new ListView(new Rect(x, y + 1, w, Math.Min(height, searchset.Count())), listsource.ToList()) - { - //LayoutStyle = LayoutStyle.Computed, + listview = new ListView(new Rect(x, y + 1, w, CalculatetHeight()), listsource.ToList()) + { + LayoutStyle = LayoutStyle.Computed, + ColorScheme = Colors.Dialog }; - + + // Needs to be re-applied for LayoutStyle.Computed + listview.X = x; + listview.Y = y + 1; + listview.Width = w; + listview.Height = CalculatetHeight (); + this.Add(listview); this.Add(search); this.SetFocus(search); - this.OnEnter += (object sender, EventArgs e) => { this.SetFocus(search); }; + this.OnEnter += (object sender, EventArgs e) => { + this.SetFocus(search); + search.CursorPosition = search.Text.Length; + }; } public override bool ProcessKey(KeyEvent e) @@ -57,23 +80,25 @@ namespace Terminal.Gui { base.ProcessKey(e); return false; // allow tab-out to next control } - - if (e.Key == Key.Enter) - { - if (Text == null) - return false; // allow tab-out to next control - search.Text = Text; + if (e.Key == Key.Enter && listview.HasFocus) { + + if (listview.Source.Count == 0 || searchset.Count == 0) { + text = ""; + return true; + } + + search.Text = text = searchset [listview.SelectedItem]; search.CursorPosition = search.Text.Length; + Changed?.Invoke (this, text); + searchset.Clear(); listview.Clear(); this.SetFocus(search); - Changed?.Invoke(this, search.Text); - return true; } - + if (e.Key == Key.CursorDown && search.HasFocus) // jump to list { this.SetFocus(listview); @@ -87,8 +112,15 @@ namespace Terminal.Gui { return true; } + if (e.Key == Key.Esc) { + this.SetFocus (search); + search.Text = text = ""; + Changed?.Invoke (this, search.Text); + return true; + } + // Unix emulation - if (e.Key == Key.ControlU || e.Key == Key.Esc) + if (e.Key == Key.ControlU) { Reset(); return true; @@ -100,14 +132,14 @@ namespace Terminal.Gui { /// /// The currenlty selected list item /// - public string Text + public ustring Text { get { - if (listview.Source.Count == 0 || searchset.Count() == 0) - return search.Text.ToString(); - - return searchset.ToList()[listview.SelectedItem] as string; + return text; + } + set { + search.Text = text = value; } } @@ -116,22 +148,52 @@ namespace Terminal.Gui { /// private void Reset() { - search.Text = ""; + search.Text = text = ""; + Changed?.Invoke (this, search.Text); searchset = listsource; + listview.SetSource(searchset.ToList()); + listview.Height = CalculatetHeight (); + listview.Redraw (new Rect (0, 0, width, height)); + this.SetFocus(search); } - private void Search_Changed(object sender, ustring e) + private void Search_Changed (object sender, ustring text) { - - if (string.IsNullOrEmpty(search.Text.ToString())) + // Cannot use text argument as its old value (pre-change) + if (string.IsNullOrEmpty (search.Text.ToString())) { searchset = listsource; + } else - searchset = listsource.Where(x => x.ToLower().Contains(search.Text.ToString().ToLower())).ToList(); + searchset = listsource.Where (x => x.StartsWith (search.Text.ToString (), StringComparison.CurrentCultureIgnoreCase)).ToList (); - listview.SetSource(searchset.ToList()); - listview.Height = Math.Min(height, searchset.Count()); + listview.SetSource (searchset.ToList ()); + listview.Height = CalculatetHeight (); + listview.Redraw (new Rect (0, 0, width, height)); + } + + /// + /// Internal height of dynamic search list + /// + /// + private int CalculatetHeight () + { + return Math.Min (height, searchset.Count); + } + + /// + /// Determine if this view is hosted inside a dialog + /// + /// + private bool IsDialogHosted() + { + for (View v = this.SuperView; v != null; v = v.SuperView) { + + if (v.GetType () == typeof (Dialog)) + return true; + } + return false; } } } From 81e3627881d74c709cb5e2eb11df1453b7dff5ec Mon Sep 17 00:00:00 2001 From: Ross Ferguson Date: Fri, 15 May 2020 06:55:17 +0100 Subject: [PATCH 03/24] Fix build --- Terminal.Gui/Views/TextFieldAutoComplete.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Terminal.Gui/Views/TextFieldAutoComplete.cs b/Terminal.Gui/Views/TextFieldAutoComplete.cs index 57befd95c..79093e1ff 100644 --- a/Terminal.Gui/Views/TextFieldAutoComplete.cs +++ b/Terminal.Gui/Views/TextFieldAutoComplete.cs @@ -67,7 +67,7 @@ namespace Terminal.Gui { this.Add(search); this.SetFocus(search); - this.OnEnter += (object sender, EventArgs e) => { + this.Enter += (object sender, EventArgs e) => { this.SetFocus(search); search.CursorPosition = search.Text.Length; }; From e1354ec31b750c826b98da8b54574d4b5bcd37fa Mon Sep 17 00:00:00 2001 From: Ross Ferguson Date: Fri, 15 May 2020 15:56:39 +0100 Subject: [PATCH 04/24] Override OnEnter() Fix per BDisp's recomendation --- Terminal.Gui/Views/TextFieldAutoComplete.cs | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/Terminal.Gui/Views/TextFieldAutoComplete.cs b/Terminal.Gui/Views/TextFieldAutoComplete.cs index 79093e1ff..bcf1a57e4 100644 --- a/Terminal.Gui/Views/TextFieldAutoComplete.cs +++ b/Terminal.Gui/Views/TextFieldAutoComplete.cs @@ -66,13 +66,17 @@ namespace Terminal.Gui { this.Add(listview); this.Add(search); this.SetFocus(search); - - this.Enter += (object sender, EventArgs e) => { - this.SetFocus(search); - search.CursorPosition = search.Text.Length; - }; } + public override bool OnEnter () + { + if (!search.HasFocus) + this.SetFocus (search); + + search.CursorPosition = search.Text.Length; + + return true; + } public override bool ProcessKey(KeyEvent e) { if (e.Key == Key.Tab) From 0f0c8e441ff9925328f02975ffb84367ab10091b Mon Sep 17 00:00:00 2001 From: Ross Ferguson Date: Sun, 17 May 2020 03:39:01 +0100 Subject: [PATCH 05/24] AutoHide option. List and text view synced. --- Example/demo.cs | 4 +- Terminal.Gui/Views/TextFieldAutoComplete.cs | 63 ++++++++++----------- 2 files changed, 34 insertions(+), 33 deletions(-) diff --git a/Example/demo.cs b/Example/demo.cs index 0fc148508..a85813d4e 100644 --- a/Example/demo.cs +++ b/Example/demo.cs @@ -415,7 +415,9 @@ static class Demo { static void TextFieldAutoCompleteDemo () { - var items = Directory.GetFiles (@"..\..\..\Terminal.Gui", "*.cs", SearchOption.AllDirectories).Select (x => Path.GetFileName (x)).ToList (); + var items = Directory.GetFiles (@"..\..\..\Terminal.Gui", "*.cs", SearchOption.AllDirectories) + .Select (x => Path.GetFileName (x)).Where(x => !x.StartsWith(".")).Distinct().OrderBy(x => x).ToList (); + var list = new TextFieldAutoComplete (0, 0, 36, 7, items); list.Changed += (object sender, ustring text) => { Application.RequestStop (); }; diff --git a/Terminal.Gui/Views/TextFieldAutoComplete.cs b/Terminal.Gui/Views/TextFieldAutoComplete.cs index bcf1a57e4..7aef2c8d6 100644 --- a/Terminal.Gui/Views/TextFieldAutoComplete.cs +++ b/Terminal.Gui/Views/TextFieldAutoComplete.cs @@ -4,9 +4,6 @@ // Author: // Ross Ferguson (ross.c.ferguson@btinternet.com) // -// TODO: -// * Completion list auto appears when hosted directly in a Window as opposed to a dialog -// using System; using System.Linq; @@ -34,6 +31,7 @@ namespace Terminal.Gui { readonly ListView listview; readonly int height; readonly int width; + readonly bool isAutoHide; /// /// Public constructor @@ -43,11 +41,15 @@ namespace Terminal.Gui { /// The width /// The height /// Auto completetion source - public TextFieldAutoComplete(int x, int y, int w, int h, IList source) + /// Completetion list hidden until start of typing. Use when hosting in Window as opposed to a Dialog + public TextFieldAutoComplete(int x, int y, int w, int h, IList source, bool autoHide = false) { - listsource = searchset = source; + listsource = source; + isAutoHide = autoHide; + searchset = isAutoHide ? new List () : listsource; height = h; width = w; + isAutoHide = autoHide; search = new TextField(x, y, w, ""); search.Changed += Search_Changed; @@ -56,6 +58,10 @@ namespace Terminal.Gui { LayoutStyle = LayoutStyle.Computed, ColorScheme = Colors.Dialog }; + listview.SelectedChanged += () => { + SetValue (searchset [listview.SelectedItem]); + }; + // Needs to be re-applied for LayoutStyle.Computed listview.X = x; @@ -92,27 +98,28 @@ namespace Terminal.Gui { return true; } - search.Text = text = searchset [listview.SelectedItem]; + SetValue( searchset [listview.SelectedItem]); search.CursorPosition = search.Text.Length; Changed?.Invoke (this, text); searchset.Clear(); - listview.Clear(); + listview.SetSource(new List ()); + listview.Height = 0; this.SetFocus(search); return true; } - if (e.Key == Key.CursorDown && search.HasFocus) // jump to list - { - this.SetFocus(listview); - listview.SelectedItem = 0; + if (e.Key == Key.CursorDown && search.HasFocus && listview.SelectedItem == 0) { // jump to list + this.SetFocus (listview); + SetValue (searchset [listview.SelectedItem]); return true; } - if(e.Key == Key.CursorUp && listview.SelectedItem == 0 && listview.HasFocus) // jump back to search + if (e.Key == Key.CursorUp && listview.HasFocus && listview.SelectedItem == 0) // jump back to search { - this.SetFocus(search); + search.CursorPosition = search.Text.Length; + this.SetFocus (search); return true; } @@ -147,6 +154,14 @@ namespace Terminal.Gui { } } + private void SetValue(ustring text) + { + search.Changed -= Search_Changed; + this.text = search.Text = text; + search.CursorPosition = 0; + search.Changed += Search_Changed; + } + /// /// Reset to full original list /// @@ -154,27 +169,25 @@ namespace Terminal.Gui { { search.Text = text = ""; Changed?.Invoke (this, search.Text); - searchset = listsource; + searchset = isAutoHide ? new List () : listsource; listview.SetSource(searchset.ToList()); listview.Height = CalculatetHeight (); - listview.Redraw (new Rect (0, 0, width, height)); this.SetFocus(search); } private void Search_Changed (object sender, ustring text) { - // Cannot use text argument as its old value (pre-change) if (string.IsNullOrEmpty (search.Text.ToString())) { - searchset = listsource; + searchset = isAutoHide ? new List () : listsource; } else searchset = listsource.Where (x => x.StartsWith (search.Text.ToString (), StringComparison.CurrentCultureIgnoreCase)).ToList (); listview.SetSource (searchset.ToList ()); listview.Height = CalculatetHeight (); - listview.Redraw (new Rect (0, 0, width, height)); + listview.Redraw (new Rect (0, 0, width, height)); // for any view behind this } /// @@ -185,19 +198,5 @@ namespace Terminal.Gui { { return Math.Min (height, searchset.Count); } - - /// - /// Determine if this view is hosted inside a dialog - /// - /// - private bool IsDialogHosted() - { - for (View v = this.SuperView; v != null; v = v.SuperView) { - - if (v.GetType () == typeof (Dialog)) - return true; - } - return false; - } } } From 2c33836719f9df21085a366cf6620281bb4b2a94 Mon Sep 17 00:00:00 2001 From: Ross Ferguson Date: Mon, 18 May 2020 06:42:23 +0100 Subject: [PATCH 06/24] TextFieldAutoComplete demo works cross-platform. Fix list clearing issue. --- Example/demo.cs | 13 ++++++++++--- Terminal.Gui/Views/TextFieldAutoComplete.cs | 14 +++++++++++++- 2 files changed, 23 insertions(+), 4 deletions(-) diff --git a/Example/demo.cs b/Example/demo.cs index a85813d4e..23bd9e850 100644 --- a/Example/demo.cs +++ b/Example/demo.cs @@ -415,9 +415,16 @@ static class Demo { static void TextFieldAutoCompleteDemo () { - var items = Directory.GetFiles (@"..\..\..\Terminal.Gui", "*.cs", SearchOption.AllDirectories) - .Select (x => Path.GetFileName (x)).Where(x => !x.StartsWith(".")).Distinct().OrderBy(x => x).ToList (); - + IList items = new List (); + foreach (var dir in new [] { "/etc", @"\windows\System32" }) { + if (Directory.Exists (dir)) { + items = Directory.GetFiles (dir) + .Select (Path.GetFileName) + .Where (x => char.IsLetterOrDigit (x [0])) + .Distinct () + .OrderBy (x => x).ToList (); + } + } var list = new TextFieldAutoComplete (0, 0, 36, 7, items); list.Changed += (object sender, ustring text) => { Application.RequestStop (); }; diff --git a/Terminal.Gui/Views/TextFieldAutoComplete.cs b/Terminal.Gui/Views/TextFieldAutoComplete.cs index 7aef2c8d6..042f8c31d 100644 --- a/Terminal.Gui/Views/TextFieldAutoComplete.cs +++ b/Terminal.Gui/Views/TextFieldAutoComplete.cs @@ -44,7 +44,7 @@ namespace Terminal.Gui { /// Completetion list hidden until start of typing. Use when hosting in Window as opposed to a Dialog public TextFieldAutoComplete(int x, int y, int w, int h, IList source, bool autoHide = false) { - listsource = source; + listsource = new List(source); isAutoHide = autoHide; searchset = isAutoHide ? new List () : listsource; height = h; @@ -74,6 +74,18 @@ namespace Terminal.Gui { this.SetFocus(search); } + public new Dim Width + { + get { return base.Width; } + set { base.Width = value; } + } + + public new Dim Height + { + get { return base.Height-1; } + set { base.Width = value+1; } + } + public override bool OnEnter () { if (!search.HasFocus) From 0e125968e9f708254d0d65f2bed76ac6462f1ee9 Mon Sep 17 00:00:00 2001 From: Ross Ferguson Date: Mon, 18 May 2020 07:15:22 +0100 Subject: [PATCH 07/24] Remove code checked-in by error --- Terminal.Gui/Views/TextFieldAutoComplete.cs | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/Terminal.Gui/Views/TextFieldAutoComplete.cs b/Terminal.Gui/Views/TextFieldAutoComplete.cs index 042f8c31d..56fea0d84 100644 --- a/Terminal.Gui/Views/TextFieldAutoComplete.cs +++ b/Terminal.Gui/Views/TextFieldAutoComplete.cs @@ -74,18 +74,6 @@ namespace Terminal.Gui { this.SetFocus(search); } - public new Dim Width - { - get { return base.Width; } - set { base.Width = value; } - } - - public new Dim Height - { - get { return base.Height-1; } - set { base.Width = value+1; } - } - public override bool OnEnter () { if (!search.HasFocus) From 2dc0204d544e2a79a832dc7e0f2a9e3c30c7cf90 Mon Sep 17 00:00:00 2001 From: Ross Ferguson Date: Tue, 19 May 2020 01:54:58 +0100 Subject: [PATCH 08/24] Detect if view is hosted inside a Dialog --- Terminal.Gui/Views/TextFieldAutoComplete.cs | 37 ++++++++++++--------- 1 file changed, 22 insertions(+), 15 deletions(-) diff --git a/Terminal.Gui/Views/TextFieldAutoComplete.cs b/Terminal.Gui/Views/TextFieldAutoComplete.cs index 56fea0d84..36192fd24 100644 --- a/Terminal.Gui/Views/TextFieldAutoComplete.cs +++ b/Terminal.Gui/Views/TextFieldAutoComplete.cs @@ -31,29 +31,25 @@ namespace Terminal.Gui { readonly ListView listview; readonly int height; readonly int width; - readonly bool isAutoHide; + bool autoHide = true; /// /// Public constructor /// /// The x coordinate /// The y coordinate - /// The width + /// The width /// The height /// Auto completetion source - /// Completetion list hidden until start of typing. Use when hosting in Window as opposed to a Dialog - public TextFieldAutoComplete(int x, int y, int w, int h, IList source, bool autoHide = false) + public TextFieldAutoComplete(int x, int y, int w, int h, IList source) { listsource = new List(source); - isAutoHide = autoHide; - searchset = isAutoHide ? new List () : listsource; height = h; width = w; - isAutoHide = autoHide; search = new TextField(x, y, w, ""); search.Changed += Search_Changed; - listview = new ListView(new Rect(x, y + 1, w, CalculatetHeight()), listsource.ToList()) + listview = new ListView(new Rect(x, y + 1, w, 0), listsource.ToList()) { LayoutStyle = LayoutStyle.Computed, ColorScheme = Colors.Dialog @@ -62,12 +58,23 @@ namespace Terminal.Gui { SetValue (searchset [listview.SelectedItem]); }; + Application.OnLoad += () => { + // Determine if this view is hosted inside a dialog + for (View view = this.SuperView; view != null; view = view.SuperView) { + if (view is Dialog) { + autoHide = false; + break; + } + } - // Needs to be re-applied for LayoutStyle.Computed - listview.X = x; - listview.Y = y + 1; - listview.Width = w; - listview.Height = CalculatetHeight (); + searchset = autoHide ? new List () : listsource; + + // Needs to be re-applied for LayoutStyle.Computed + listview.X = x; + listview.Y = y + 1; + listview.Width = w; + listview.Height = CalculatetHeight (); + }; this.Add(listview); this.Add(search); @@ -169,7 +176,7 @@ namespace Terminal.Gui { { search.Text = text = ""; Changed?.Invoke (this, search.Text); - searchset = isAutoHide ? new List () : listsource; + searchset = autoHide ? new List () : listsource; listview.SetSource(searchset.ToList()); listview.Height = CalculatetHeight (); @@ -180,7 +187,7 @@ namespace Terminal.Gui { private void Search_Changed (object sender, ustring text) { if (string.IsNullOrEmpty (search.Text.ToString())) { - searchset = isAutoHide ? new List () : listsource; + searchset = autoHide ? new List () : listsource; } else searchset = listsource.Where (x => x.StartsWith (search.Text.ToString (), StringComparison.CurrentCultureIgnoreCase)).ToList (); From 250efb96cda236ba653d506fb21d792a3284e130 Mon Sep 17 00:00:00 2001 From: Ross Ferguson Date: Wed, 20 May 2020 03:34:59 +0100 Subject: [PATCH 09/24] Make sure view is at front when shown --- Terminal.Gui/Views/TextFieldAutoComplete.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Terminal.Gui/Views/TextFieldAutoComplete.cs b/Terminal.Gui/Views/TextFieldAutoComplete.cs index 36192fd24..8bee85a93 100644 --- a/Terminal.Gui/Views/TextFieldAutoComplete.cs +++ b/Terminal.Gui/Views/TextFieldAutoComplete.cs @@ -194,7 +194,9 @@ namespace Terminal.Gui { listview.SetSource (searchset.ToList ()); listview.Height = CalculatetHeight (); + listview.Redraw (new Rect (0, 0, width, height)); // for any view behind this + this.SuperView?.BringSubviewToFront (this); } /// From 4af21d3a9e758be196cbd802272a74ef3ca25de2 Mon Sep 17 00:00:00 2001 From: Ross Ferguson Date: Thu, 21 May 2020 09:25:21 +0100 Subject: [PATCH 10/24] Rename TextViewAutoComplete to ComboBox --- Example/demo.cs | 6 +++--- .../{TextFieldAutoComplete.cs => ComboBox.cs} | 18 ++++++++++-------- 2 files changed, 13 insertions(+), 11 deletions(-) rename Terminal.Gui/Views/{TextFieldAutoComplete.cs => ComboBox.cs} (92%) diff --git a/Example/demo.cs b/Example/demo.cs index 23bd9e850..98dd80af7 100644 --- a/Example/demo.cs +++ b/Example/demo.cs @@ -413,7 +413,7 @@ static class Demo { MessageBox.Query (60, 10, "Selected Animals", result == "" ? "No animals selected" : result, "Ok"); } - static void TextFieldAutoCompleteDemo () + static void ComboBoxDemo () { IList items = new List (); foreach (var dir in new [] { "/etc", @"\windows\System32" }) { @@ -425,7 +425,7 @@ static class Demo { .OrderBy (x => x).ToList (); } } - var list = new TextFieldAutoComplete (0, 0, 36, 7, items); + var list = new ComboBox (0, 0, 36, 7, items); list.Changed += (object sender, ustring text) => { Application.RequestStop (); }; var d = new Dialog ("Select source file", 40, 12) { list }; @@ -563,7 +563,7 @@ static class Demo { new MenuBarItem ("_List Demos", new MenuItem [] { new MenuItem ("Select _Multiple Items", "", () => ListSelectionDemo (true)), new MenuItem ("Select _Single Item", "", () => ListSelectionDemo (false)), - new MenuItem ("Search Single Item", "", TextFieldAutoCompleteDemo) + new MenuItem ("Search Single Item", "", ComboBoxDemo) }), new MenuBarItem ("A_ssorted", new MenuItem [] { new MenuItem ("_Show text alignments", "", () => ShowTextAlignments ()), diff --git a/Terminal.Gui/Views/TextFieldAutoComplete.cs b/Terminal.Gui/Views/ComboBox.cs similarity index 92% rename from Terminal.Gui/Views/TextFieldAutoComplete.cs rename to Terminal.Gui/Views/ComboBox.cs index 8bee85a93..b04d6dbc3 100644 --- a/Terminal.Gui/Views/TextFieldAutoComplete.cs +++ b/Terminal.Gui/Views/ComboBox.cs @@ -1,7 +1,7 @@ // -// TextFieldAutoComplete.cs: TextField with AutoComplete +// ComboBox.cs: ComboBox control // -// Author: +// Authors: // Ross Ferguson (ross.c.ferguson@btinternet.com) // @@ -12,9 +12,9 @@ using NStack; namespace Terminal.Gui { /// - /// TextField with AutoComplete + /// ComboBox control /// - public class TextFieldAutoComplete : View { + public class ComboBox : View { /// /// Changed event, raised when the selection has been confirmed. /// @@ -41,7 +41,7 @@ namespace Terminal.Gui { /// The width /// The height /// Auto completetion source - public TextFieldAutoComplete(int x, int y, int w, int h, IList source) + public ComboBox(int x, int y, int w, int h, IList source) { listsource = new List(source); height = h; @@ -55,7 +55,8 @@ namespace Terminal.Gui { ColorScheme = Colors.Dialog }; listview.SelectedChanged += () => { - SetValue (searchset [listview.SelectedItem]); + if(searchset.Count > 0) + SetValue (searchset [listview.SelectedItem]); }; Application.OnLoad += () => { @@ -90,6 +91,7 @@ namespace Terminal.Gui { return true; } + public override bool ProcessKey(KeyEvent e) { if (e.Key == Key.Tab) @@ -117,13 +119,13 @@ namespace Terminal.Gui { return true; } - if (e.Key == Key.CursorDown && search.HasFocus && listview.SelectedItem == 0) { // jump to list + if (e.Key == Key.CursorDown && search.HasFocus && listview.SelectedItem == 0 && searchset.Count > 0) { // jump to list this.SetFocus (listview); SetValue (searchset [listview.SelectedItem]); return true; } - if (e.Key == Key.CursorUp && listview.HasFocus && listview.SelectedItem == 0) // jump back to search + if (e.Key == Key.CursorUp && listview.HasFocus && listview.SelectedItem == 0 && searchset.Count > 0) // jump back to search { search.CursorPosition = search.Text.Length; this.SetFocus (search); From e04cbf2b97fe4fe752cb85d9802313505d5b11d8 Mon Sep 17 00:00:00 2001 From: Ross Ferguson Date: Sat, 9 May 2020 07:00:14 +0100 Subject: [PATCH 11/24] Feature/TextFieldAutoComplete --- Example/demo.cs | 15 +++ Terminal.Gui/Views/TextFieldAutoComplete.cs | 137 ++++++++++++++++++++ 2 files changed, 152 insertions(+) create mode 100644 Terminal.Gui/Views/TextFieldAutoComplete.cs diff --git a/Example/demo.cs b/Example/demo.cs index 6db1bb40b..76e24ddbc 100644 --- a/Example/demo.cs +++ b/Example/demo.cs @@ -1,5 +1,7 @@ using Terminal.Gui; using System; +using System.Linq; +using System.IO; using Mono.Terminal; using System.Collections.Generic; using System.Diagnostics; @@ -410,6 +412,18 @@ static class Demo { } MessageBox.Query (60, 10, "Selected Animals", result == "" ? "No animals selected" : result, "Ok"); } + + static void TextFieldAutoCompleteDemo () + { + var items = Directory.GetFiles (@"..\..\..\Terminal.Gui", "*.cs", SearchOption.AllDirectories).Select (x => Path.GetFileName (x)).ToList (); + var list = new TextFieldAutoComplete (0, 0, 36, 7, items); + list.Changed += (object sender, ustring text) => { Application.RequestStop (); }; + + var d = new Dialog ("Select source file", 40, 12) { list }; + Application.Run (d); + + MessageBox.Query (60, 10, "Selected file", list.Text == null ? "Nothing selected" : list.Text, "Ok"); + } #endregion @@ -540,6 +554,7 @@ static class Demo { new MenuBarItem ("_List Demos", new MenuItem [] { new MenuItem ("Select _Multiple Items", "", () => ListSelectionDemo (true)), new MenuItem ("Select _Single Item", "", () => ListSelectionDemo (false)), + new MenuItem ("Search Single Item", "", TextFieldAutoCompleteDemo) }), new MenuBarItem ("A_ssorted", new MenuItem [] { new MenuItem ("_Show text alignments", "", () => ShowTextAlignments ()), diff --git a/Terminal.Gui/Views/TextFieldAutoComplete.cs b/Terminal.Gui/Views/TextFieldAutoComplete.cs new file mode 100644 index 000000000..3c8c1f210 --- /dev/null +++ b/Terminal.Gui/Views/TextFieldAutoComplete.cs @@ -0,0 +1,137 @@ +// +// TextFieldAutoComplete.cs: TextField with AutoComplete +// +// Author: +// Ross Ferguson (ross.c.ferguson@btinternet.com) +// + +using System; +using System.Linq; +using System.Collections.Generic; +using NStack; + +namespace Terminal.Gui { + /// + /// TextField with AutoComplete + /// + public class TextFieldAutoComplete : View { + public event EventHandler Changed; + + readonly IList listsource; + IList searchset; + readonly TextField search; + readonly ListView listview; + readonly int height; + + /// + /// Public constructor + /// + /// The x coordinate + /// The y coordinate + /// The width + /// The height + /// Auto completetion source + public TextFieldAutoComplete(int x, int y, int w, int h, IList source) : base() + { + listsource = searchset = source; + height = h; + search = new TextField(x, y, w, ""); + search.Changed += Search_Changed; + + listview = new ListView(new Rect(x, y + 1, w, Math.Min(height, searchset.Count())), listsource.ToList()) + { + //LayoutStyle = LayoutStyle.Computed, + }; + + this.Add(listview); + this.Add(search); + this.SetFocus(search); + + this.OnEnter += (object sender, EventArgs e) => { this.SetFocus(search); }; + } + + public override bool ProcessKey(KeyEvent e) + { + if (e.Key == Key.Tab) + { + base.ProcessKey(e); + return false; // allow tab-out to next control + } + + if (e.Key == Key.Enter) + { + if (Text == null) + return false; // allow tab-out to next control + + search.Text = Text; + search.CursorPosition = search.Text.Length; + searchset.Clear(); + listview.Clear(); + this.SetFocus(search); + + Changed?.Invoke(this, search.Text); + + return true; + } + + if (e.Key == Key.CursorDown && search.HasFocus) // jump to list + { + this.SetFocus(listview); + listview.SelectedItem = 0; + return true; + } + + if(e.Key == Key.CursorUp && listview.SelectedItem == 0 && listview.HasFocus) // jump back to search + { + this.SetFocus(search); + return true; + } + + // Unix emulation + if (e.Key == Key.ControlU || e.Key == Key.Esc) + { + Reset(); + return true; + } + + return base.ProcessKey(e); + } + + /// + /// The currenlty selected list item + /// + public string Text + { + get + { + if (listview.Source.Count == 0 || searchset.Count() == 0) + return search.Text.ToString(); + + return searchset.ToList()[listview.SelectedItem] as string; + } + } + + /// + /// Reset to full original list + /// + private void Reset() + { + search.Text = ""; + searchset = listsource; + listview.SetSource(searchset.ToList()); + this.SetFocus(search); + } + + private void Search_Changed(object sender, ustring e) + { + + if (string.IsNullOrEmpty(search.Text.ToString())) + searchset = listsource; + else + searchset = listsource.Where(x => x.ToLower().Contains(search.Text.ToString().ToLower())).ToList(); + + listview.SetSource(searchset.ToList()); + listview.Height = Math.Min(height, searchset.Count()); + } + } +} From 1dc0cc6fac98c4eb2529c83422e3597fed9ebb15 Mon Sep 17 00:00:00 2001 From: Ross Ferguson Date: Fri, 15 May 2020 06:04:38 +0100 Subject: [PATCH 12/24] Use LayoutStyle.Computed. Add Text setter --- Example/demo.cs | 2 +- Terminal.Gui/Views/TextFieldAutoComplete.cs | 118 +++++++++++++++----- 2 files changed, 91 insertions(+), 29 deletions(-) diff --git a/Example/demo.cs b/Example/demo.cs index 76e24ddbc..0fc148508 100644 --- a/Example/demo.cs +++ b/Example/demo.cs @@ -422,7 +422,7 @@ static class Demo { var d = new Dialog ("Select source file", 40, 12) { list }; Application.Run (d); - MessageBox.Query (60, 10, "Selected file", list.Text == null ? "Nothing selected" : list.Text, "Ok"); + MessageBox.Query (60, 10, "Selected file", list.Text.ToString() == "" ? "Nothing selected" : list.Text.ToString(), "Ok"); } #endregion diff --git a/Terminal.Gui/Views/TextFieldAutoComplete.cs b/Terminal.Gui/Views/TextFieldAutoComplete.cs index 3c8c1f210..57befd95c 100644 --- a/Terminal.Gui/Views/TextFieldAutoComplete.cs +++ b/Terminal.Gui/Views/TextFieldAutoComplete.cs @@ -4,6 +4,9 @@ // Author: // Ross Ferguson (ross.c.ferguson@btinternet.com) // +// TODO: +// * Completion list auto appears when hosted directly in a Window as opposed to a dialog +// using System; using System.Linq; @@ -15,13 +18,22 @@ namespace Terminal.Gui { /// TextField with AutoComplete /// public class TextFieldAutoComplete : View { + /// + /// Changed event, raised when the selection has been confirmed. + /// + /// + /// Client code can hook up to this event, it is + /// raised when the selection has been confirmed. + /// public event EventHandler Changed; readonly IList listsource; IList searchset; + ustring text = ""; readonly TextField search; readonly ListView listview; readonly int height; + readonly int width; /// /// Public constructor @@ -31,23 +43,34 @@ namespace Terminal.Gui { /// The width /// The height /// Auto completetion source - public TextFieldAutoComplete(int x, int y, int w, int h, IList source) : base() + public TextFieldAutoComplete(int x, int y, int w, int h, IList source) { listsource = searchset = source; height = h; + width = w; search = new TextField(x, y, w, ""); search.Changed += Search_Changed; - listview = new ListView(new Rect(x, y + 1, w, Math.Min(height, searchset.Count())), listsource.ToList()) - { - //LayoutStyle = LayoutStyle.Computed, + listview = new ListView(new Rect(x, y + 1, w, CalculatetHeight()), listsource.ToList()) + { + LayoutStyle = LayoutStyle.Computed, + ColorScheme = Colors.Dialog }; - + + // Needs to be re-applied for LayoutStyle.Computed + listview.X = x; + listview.Y = y + 1; + listview.Width = w; + listview.Height = CalculatetHeight (); + this.Add(listview); this.Add(search); this.SetFocus(search); - this.OnEnter += (object sender, EventArgs e) => { this.SetFocus(search); }; + this.OnEnter += (object sender, EventArgs e) => { + this.SetFocus(search); + search.CursorPosition = search.Text.Length; + }; } public override bool ProcessKey(KeyEvent e) @@ -57,23 +80,25 @@ namespace Terminal.Gui { base.ProcessKey(e); return false; // allow tab-out to next control } - - if (e.Key == Key.Enter) - { - if (Text == null) - return false; // allow tab-out to next control - search.Text = Text; + if (e.Key == Key.Enter && listview.HasFocus) { + + if (listview.Source.Count == 0 || searchset.Count == 0) { + text = ""; + return true; + } + + search.Text = text = searchset [listview.SelectedItem]; search.CursorPosition = search.Text.Length; + Changed?.Invoke (this, text); + searchset.Clear(); listview.Clear(); this.SetFocus(search); - Changed?.Invoke(this, search.Text); - return true; } - + if (e.Key == Key.CursorDown && search.HasFocus) // jump to list { this.SetFocus(listview); @@ -87,8 +112,15 @@ namespace Terminal.Gui { return true; } + if (e.Key == Key.Esc) { + this.SetFocus (search); + search.Text = text = ""; + Changed?.Invoke (this, search.Text); + return true; + } + // Unix emulation - if (e.Key == Key.ControlU || e.Key == Key.Esc) + if (e.Key == Key.ControlU) { Reset(); return true; @@ -100,14 +132,14 @@ namespace Terminal.Gui { /// /// The currenlty selected list item /// - public string Text + public ustring Text { get { - if (listview.Source.Count == 0 || searchset.Count() == 0) - return search.Text.ToString(); - - return searchset.ToList()[listview.SelectedItem] as string; + return text; + } + set { + search.Text = text = value; } } @@ -116,22 +148,52 @@ namespace Terminal.Gui { /// private void Reset() { - search.Text = ""; + search.Text = text = ""; + Changed?.Invoke (this, search.Text); searchset = listsource; + listview.SetSource(searchset.ToList()); + listview.Height = CalculatetHeight (); + listview.Redraw (new Rect (0, 0, width, height)); + this.SetFocus(search); } - private void Search_Changed(object sender, ustring e) + private void Search_Changed (object sender, ustring text) { - - if (string.IsNullOrEmpty(search.Text.ToString())) + // Cannot use text argument as its old value (pre-change) + if (string.IsNullOrEmpty (search.Text.ToString())) { searchset = listsource; + } else - searchset = listsource.Where(x => x.ToLower().Contains(search.Text.ToString().ToLower())).ToList(); + searchset = listsource.Where (x => x.StartsWith (search.Text.ToString (), StringComparison.CurrentCultureIgnoreCase)).ToList (); - listview.SetSource(searchset.ToList()); - listview.Height = Math.Min(height, searchset.Count()); + listview.SetSource (searchset.ToList ()); + listview.Height = CalculatetHeight (); + listview.Redraw (new Rect (0, 0, width, height)); + } + + /// + /// Internal height of dynamic search list + /// + /// + private int CalculatetHeight () + { + return Math.Min (height, searchset.Count); + } + + /// + /// Determine if this view is hosted inside a dialog + /// + /// + private bool IsDialogHosted() + { + for (View v = this.SuperView; v != null; v = v.SuperView) { + + if (v.GetType () == typeof (Dialog)) + return true; + } + return false; } } } From a5ae6f0176c9a26e243820df42914f46ace231d7 Mon Sep 17 00:00:00 2001 From: Ross Ferguson Date: Fri, 15 May 2020 06:55:17 +0100 Subject: [PATCH 13/24] Fix build --- Terminal.Gui/Views/TextFieldAutoComplete.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Terminal.Gui/Views/TextFieldAutoComplete.cs b/Terminal.Gui/Views/TextFieldAutoComplete.cs index 57befd95c..79093e1ff 100644 --- a/Terminal.Gui/Views/TextFieldAutoComplete.cs +++ b/Terminal.Gui/Views/TextFieldAutoComplete.cs @@ -67,7 +67,7 @@ namespace Terminal.Gui { this.Add(search); this.SetFocus(search); - this.OnEnter += (object sender, EventArgs e) => { + this.Enter += (object sender, EventArgs e) => { this.SetFocus(search); search.CursorPosition = search.Text.Length; }; From 9298ce19d1b0bf528d84919f5f34a576f2bbe005 Mon Sep 17 00:00:00 2001 From: Ross Ferguson Date: Fri, 15 May 2020 15:56:39 +0100 Subject: [PATCH 14/24] Override OnEnter() Fix per BDisp's recomendation --- Terminal.Gui/Views/TextFieldAutoComplete.cs | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/Terminal.Gui/Views/TextFieldAutoComplete.cs b/Terminal.Gui/Views/TextFieldAutoComplete.cs index 79093e1ff..bcf1a57e4 100644 --- a/Terminal.Gui/Views/TextFieldAutoComplete.cs +++ b/Terminal.Gui/Views/TextFieldAutoComplete.cs @@ -66,13 +66,17 @@ namespace Terminal.Gui { this.Add(listview); this.Add(search); this.SetFocus(search); - - this.Enter += (object sender, EventArgs e) => { - this.SetFocus(search); - search.CursorPosition = search.Text.Length; - }; } + public override bool OnEnter () + { + if (!search.HasFocus) + this.SetFocus (search); + + search.CursorPosition = search.Text.Length; + + return true; + } public override bool ProcessKey(KeyEvent e) { if (e.Key == Key.Tab) From 282cf645004111a291f1261f411409b7cb074e95 Mon Sep 17 00:00:00 2001 From: Ross Ferguson Date: Sun, 17 May 2020 03:39:01 +0100 Subject: [PATCH 15/24] AutoHide option. List and text view synced. --- Example/demo.cs | 4 +- Terminal.Gui/Views/TextFieldAutoComplete.cs | 63 ++++++++++----------- 2 files changed, 34 insertions(+), 33 deletions(-) diff --git a/Example/demo.cs b/Example/demo.cs index 0fc148508..a85813d4e 100644 --- a/Example/demo.cs +++ b/Example/demo.cs @@ -415,7 +415,9 @@ static class Demo { static void TextFieldAutoCompleteDemo () { - var items = Directory.GetFiles (@"..\..\..\Terminal.Gui", "*.cs", SearchOption.AllDirectories).Select (x => Path.GetFileName (x)).ToList (); + var items = Directory.GetFiles (@"..\..\..\Terminal.Gui", "*.cs", SearchOption.AllDirectories) + .Select (x => Path.GetFileName (x)).Where(x => !x.StartsWith(".")).Distinct().OrderBy(x => x).ToList (); + var list = new TextFieldAutoComplete (0, 0, 36, 7, items); list.Changed += (object sender, ustring text) => { Application.RequestStop (); }; diff --git a/Terminal.Gui/Views/TextFieldAutoComplete.cs b/Terminal.Gui/Views/TextFieldAutoComplete.cs index bcf1a57e4..7aef2c8d6 100644 --- a/Terminal.Gui/Views/TextFieldAutoComplete.cs +++ b/Terminal.Gui/Views/TextFieldAutoComplete.cs @@ -4,9 +4,6 @@ // Author: // Ross Ferguson (ross.c.ferguson@btinternet.com) // -// TODO: -// * Completion list auto appears when hosted directly in a Window as opposed to a dialog -// using System; using System.Linq; @@ -34,6 +31,7 @@ namespace Terminal.Gui { readonly ListView listview; readonly int height; readonly int width; + readonly bool isAutoHide; /// /// Public constructor @@ -43,11 +41,15 @@ namespace Terminal.Gui { /// The width /// The height /// Auto completetion source - public TextFieldAutoComplete(int x, int y, int w, int h, IList source) + /// Completetion list hidden until start of typing. Use when hosting in Window as opposed to a Dialog + public TextFieldAutoComplete(int x, int y, int w, int h, IList source, bool autoHide = false) { - listsource = searchset = source; + listsource = source; + isAutoHide = autoHide; + searchset = isAutoHide ? new List () : listsource; height = h; width = w; + isAutoHide = autoHide; search = new TextField(x, y, w, ""); search.Changed += Search_Changed; @@ -56,6 +58,10 @@ namespace Terminal.Gui { LayoutStyle = LayoutStyle.Computed, ColorScheme = Colors.Dialog }; + listview.SelectedChanged += () => { + SetValue (searchset [listview.SelectedItem]); + }; + // Needs to be re-applied for LayoutStyle.Computed listview.X = x; @@ -92,27 +98,28 @@ namespace Terminal.Gui { return true; } - search.Text = text = searchset [listview.SelectedItem]; + SetValue( searchset [listview.SelectedItem]); search.CursorPosition = search.Text.Length; Changed?.Invoke (this, text); searchset.Clear(); - listview.Clear(); + listview.SetSource(new List ()); + listview.Height = 0; this.SetFocus(search); return true; } - if (e.Key == Key.CursorDown && search.HasFocus) // jump to list - { - this.SetFocus(listview); - listview.SelectedItem = 0; + if (e.Key == Key.CursorDown && search.HasFocus && listview.SelectedItem == 0) { // jump to list + this.SetFocus (listview); + SetValue (searchset [listview.SelectedItem]); return true; } - if(e.Key == Key.CursorUp && listview.SelectedItem == 0 && listview.HasFocus) // jump back to search + if (e.Key == Key.CursorUp && listview.HasFocus && listview.SelectedItem == 0) // jump back to search { - this.SetFocus(search); + search.CursorPosition = search.Text.Length; + this.SetFocus (search); return true; } @@ -147,6 +154,14 @@ namespace Terminal.Gui { } } + private void SetValue(ustring text) + { + search.Changed -= Search_Changed; + this.text = search.Text = text; + search.CursorPosition = 0; + search.Changed += Search_Changed; + } + /// /// Reset to full original list /// @@ -154,27 +169,25 @@ namespace Terminal.Gui { { search.Text = text = ""; Changed?.Invoke (this, search.Text); - searchset = listsource; + searchset = isAutoHide ? new List () : listsource; listview.SetSource(searchset.ToList()); listview.Height = CalculatetHeight (); - listview.Redraw (new Rect (0, 0, width, height)); this.SetFocus(search); } private void Search_Changed (object sender, ustring text) { - // Cannot use text argument as its old value (pre-change) if (string.IsNullOrEmpty (search.Text.ToString())) { - searchset = listsource; + searchset = isAutoHide ? new List () : listsource; } else searchset = listsource.Where (x => x.StartsWith (search.Text.ToString (), StringComparison.CurrentCultureIgnoreCase)).ToList (); listview.SetSource (searchset.ToList ()); listview.Height = CalculatetHeight (); - listview.Redraw (new Rect (0, 0, width, height)); + listview.Redraw (new Rect (0, 0, width, height)); // for any view behind this } /// @@ -185,19 +198,5 @@ namespace Terminal.Gui { { return Math.Min (height, searchset.Count); } - - /// - /// Determine if this view is hosted inside a dialog - /// - /// - private bool IsDialogHosted() - { - for (View v = this.SuperView; v != null; v = v.SuperView) { - - if (v.GetType () == typeof (Dialog)) - return true; - } - return false; - } } } From 360e0256b8e56055e0f371cdf6b0ee6f39c51d08 Mon Sep 17 00:00:00 2001 From: Ross Ferguson Date: Mon, 18 May 2020 06:42:23 +0100 Subject: [PATCH 16/24] TextFieldAutoComplete demo works cross-platform. Fix list clearing issue. --- Example/demo.cs | 13 ++++++++++--- Terminal.Gui/Views/TextFieldAutoComplete.cs | 14 +++++++++++++- 2 files changed, 23 insertions(+), 4 deletions(-) diff --git a/Example/demo.cs b/Example/demo.cs index a85813d4e..23bd9e850 100644 --- a/Example/demo.cs +++ b/Example/demo.cs @@ -415,9 +415,16 @@ static class Demo { static void TextFieldAutoCompleteDemo () { - var items = Directory.GetFiles (@"..\..\..\Terminal.Gui", "*.cs", SearchOption.AllDirectories) - .Select (x => Path.GetFileName (x)).Where(x => !x.StartsWith(".")).Distinct().OrderBy(x => x).ToList (); - + IList items = new List (); + foreach (var dir in new [] { "/etc", @"\windows\System32" }) { + if (Directory.Exists (dir)) { + items = Directory.GetFiles (dir) + .Select (Path.GetFileName) + .Where (x => char.IsLetterOrDigit (x [0])) + .Distinct () + .OrderBy (x => x).ToList (); + } + } var list = new TextFieldAutoComplete (0, 0, 36, 7, items); list.Changed += (object sender, ustring text) => { Application.RequestStop (); }; diff --git a/Terminal.Gui/Views/TextFieldAutoComplete.cs b/Terminal.Gui/Views/TextFieldAutoComplete.cs index 7aef2c8d6..042f8c31d 100644 --- a/Terminal.Gui/Views/TextFieldAutoComplete.cs +++ b/Terminal.Gui/Views/TextFieldAutoComplete.cs @@ -44,7 +44,7 @@ namespace Terminal.Gui { /// Completetion list hidden until start of typing. Use when hosting in Window as opposed to a Dialog public TextFieldAutoComplete(int x, int y, int w, int h, IList source, bool autoHide = false) { - listsource = source; + listsource = new List(source); isAutoHide = autoHide; searchset = isAutoHide ? new List () : listsource; height = h; @@ -74,6 +74,18 @@ namespace Terminal.Gui { this.SetFocus(search); } + public new Dim Width + { + get { return base.Width; } + set { base.Width = value; } + } + + public new Dim Height + { + get { return base.Height-1; } + set { base.Width = value+1; } + } + public override bool OnEnter () { if (!search.HasFocus) From de21cfe8ed0ce1502acafd56799be7272ad37d4c Mon Sep 17 00:00:00 2001 From: Ross Ferguson Date: Mon, 18 May 2020 07:15:22 +0100 Subject: [PATCH 17/24] Remove code checked-in by error --- Terminal.Gui/Views/TextFieldAutoComplete.cs | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/Terminal.Gui/Views/TextFieldAutoComplete.cs b/Terminal.Gui/Views/TextFieldAutoComplete.cs index 042f8c31d..56fea0d84 100644 --- a/Terminal.Gui/Views/TextFieldAutoComplete.cs +++ b/Terminal.Gui/Views/TextFieldAutoComplete.cs @@ -74,18 +74,6 @@ namespace Terminal.Gui { this.SetFocus(search); } - public new Dim Width - { - get { return base.Width; } - set { base.Width = value; } - } - - public new Dim Height - { - get { return base.Height-1; } - set { base.Width = value+1; } - } - public override bool OnEnter () { if (!search.HasFocus) From 2f285f485ac3b71faffa4488d70a9eacd4a6e8f0 Mon Sep 17 00:00:00 2001 From: Ross Ferguson Date: Tue, 19 May 2020 01:54:58 +0100 Subject: [PATCH 18/24] Detect if view is hosted inside a Dialog --- Terminal.Gui/Views/TextFieldAutoComplete.cs | 37 ++++++++++++--------- 1 file changed, 22 insertions(+), 15 deletions(-) diff --git a/Terminal.Gui/Views/TextFieldAutoComplete.cs b/Terminal.Gui/Views/TextFieldAutoComplete.cs index 56fea0d84..36192fd24 100644 --- a/Terminal.Gui/Views/TextFieldAutoComplete.cs +++ b/Terminal.Gui/Views/TextFieldAutoComplete.cs @@ -31,29 +31,25 @@ namespace Terminal.Gui { readonly ListView listview; readonly int height; readonly int width; - readonly bool isAutoHide; + bool autoHide = true; /// /// Public constructor /// /// The x coordinate /// The y coordinate - /// The width + /// The width /// The height /// Auto completetion source - /// Completetion list hidden until start of typing. Use when hosting in Window as opposed to a Dialog - public TextFieldAutoComplete(int x, int y, int w, int h, IList source, bool autoHide = false) + public TextFieldAutoComplete(int x, int y, int w, int h, IList source) { listsource = new List(source); - isAutoHide = autoHide; - searchset = isAutoHide ? new List () : listsource; height = h; width = w; - isAutoHide = autoHide; search = new TextField(x, y, w, ""); search.Changed += Search_Changed; - listview = new ListView(new Rect(x, y + 1, w, CalculatetHeight()), listsource.ToList()) + listview = new ListView(new Rect(x, y + 1, w, 0), listsource.ToList()) { LayoutStyle = LayoutStyle.Computed, ColorScheme = Colors.Dialog @@ -62,12 +58,23 @@ namespace Terminal.Gui { SetValue (searchset [listview.SelectedItem]); }; + Application.OnLoad += () => { + // Determine if this view is hosted inside a dialog + for (View view = this.SuperView; view != null; view = view.SuperView) { + if (view is Dialog) { + autoHide = false; + break; + } + } - // Needs to be re-applied for LayoutStyle.Computed - listview.X = x; - listview.Y = y + 1; - listview.Width = w; - listview.Height = CalculatetHeight (); + searchset = autoHide ? new List () : listsource; + + // Needs to be re-applied for LayoutStyle.Computed + listview.X = x; + listview.Y = y + 1; + listview.Width = w; + listview.Height = CalculatetHeight (); + }; this.Add(listview); this.Add(search); @@ -169,7 +176,7 @@ namespace Terminal.Gui { { search.Text = text = ""; Changed?.Invoke (this, search.Text); - searchset = isAutoHide ? new List () : listsource; + searchset = autoHide ? new List () : listsource; listview.SetSource(searchset.ToList()); listview.Height = CalculatetHeight (); @@ -180,7 +187,7 @@ namespace Terminal.Gui { private void Search_Changed (object sender, ustring text) { if (string.IsNullOrEmpty (search.Text.ToString())) { - searchset = isAutoHide ? new List () : listsource; + searchset = autoHide ? new List () : listsource; } else searchset = listsource.Where (x => x.StartsWith (search.Text.ToString (), StringComparison.CurrentCultureIgnoreCase)).ToList (); From 01c83e8983f56ab183098b6b6ad6761e62124be9 Mon Sep 17 00:00:00 2001 From: Ross Ferguson Date: Wed, 20 May 2020 03:34:59 +0100 Subject: [PATCH 19/24] Make sure view is at front when shown --- Terminal.Gui/Views/TextFieldAutoComplete.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Terminal.Gui/Views/TextFieldAutoComplete.cs b/Terminal.Gui/Views/TextFieldAutoComplete.cs index 36192fd24..8bee85a93 100644 --- a/Terminal.Gui/Views/TextFieldAutoComplete.cs +++ b/Terminal.Gui/Views/TextFieldAutoComplete.cs @@ -194,7 +194,9 @@ namespace Terminal.Gui { listview.SetSource (searchset.ToList ()); listview.Height = CalculatetHeight (); + listview.Redraw (new Rect (0, 0, width, height)); // for any view behind this + this.SuperView?.BringSubviewToFront (this); } /// From f0199f6e611e6de8d9aa39d8246577c8583d07da Mon Sep 17 00:00:00 2001 From: Ross Ferguson Date: Thu, 21 May 2020 09:25:21 +0100 Subject: [PATCH 20/24] Rename TextViewAutoComplete to ComboBox --- Example/demo.cs | 6 +++--- .../{TextFieldAutoComplete.cs => ComboBox.cs} | 18 ++++++++++-------- 2 files changed, 13 insertions(+), 11 deletions(-) rename Terminal.Gui/Views/{TextFieldAutoComplete.cs => ComboBox.cs} (92%) diff --git a/Example/demo.cs b/Example/demo.cs index 23bd9e850..98dd80af7 100644 --- a/Example/demo.cs +++ b/Example/demo.cs @@ -413,7 +413,7 @@ static class Demo { MessageBox.Query (60, 10, "Selected Animals", result == "" ? "No animals selected" : result, "Ok"); } - static void TextFieldAutoCompleteDemo () + static void ComboBoxDemo () { IList items = new List (); foreach (var dir in new [] { "/etc", @"\windows\System32" }) { @@ -425,7 +425,7 @@ static class Demo { .OrderBy (x => x).ToList (); } } - var list = new TextFieldAutoComplete (0, 0, 36, 7, items); + var list = new ComboBox (0, 0, 36, 7, items); list.Changed += (object sender, ustring text) => { Application.RequestStop (); }; var d = new Dialog ("Select source file", 40, 12) { list }; @@ -563,7 +563,7 @@ static class Demo { new MenuBarItem ("_List Demos", new MenuItem [] { new MenuItem ("Select _Multiple Items", "", () => ListSelectionDemo (true)), new MenuItem ("Select _Single Item", "", () => ListSelectionDemo (false)), - new MenuItem ("Search Single Item", "", TextFieldAutoCompleteDemo) + new MenuItem ("Search Single Item", "", ComboBoxDemo) }), new MenuBarItem ("A_ssorted", new MenuItem [] { new MenuItem ("_Show text alignments", "", () => ShowTextAlignments ()), diff --git a/Terminal.Gui/Views/TextFieldAutoComplete.cs b/Terminal.Gui/Views/ComboBox.cs similarity index 92% rename from Terminal.Gui/Views/TextFieldAutoComplete.cs rename to Terminal.Gui/Views/ComboBox.cs index 8bee85a93..b04d6dbc3 100644 --- a/Terminal.Gui/Views/TextFieldAutoComplete.cs +++ b/Terminal.Gui/Views/ComboBox.cs @@ -1,7 +1,7 @@ // -// TextFieldAutoComplete.cs: TextField with AutoComplete +// ComboBox.cs: ComboBox control // -// Author: +// Authors: // Ross Ferguson (ross.c.ferguson@btinternet.com) // @@ -12,9 +12,9 @@ using NStack; namespace Terminal.Gui { /// - /// TextField with AutoComplete + /// ComboBox control /// - public class TextFieldAutoComplete : View { + public class ComboBox : View { /// /// Changed event, raised when the selection has been confirmed. /// @@ -41,7 +41,7 @@ namespace Terminal.Gui { /// The width /// The height /// Auto completetion source - public TextFieldAutoComplete(int x, int y, int w, int h, IList source) + public ComboBox(int x, int y, int w, int h, IList source) { listsource = new List(source); height = h; @@ -55,7 +55,8 @@ namespace Terminal.Gui { ColorScheme = Colors.Dialog }; listview.SelectedChanged += () => { - SetValue (searchset [listview.SelectedItem]); + if(searchset.Count > 0) + SetValue (searchset [listview.SelectedItem]); }; Application.OnLoad += () => { @@ -90,6 +91,7 @@ namespace Terminal.Gui { return true; } + public override bool ProcessKey(KeyEvent e) { if (e.Key == Key.Tab) @@ -117,13 +119,13 @@ namespace Terminal.Gui { return true; } - if (e.Key == Key.CursorDown && search.HasFocus && listview.SelectedItem == 0) { // jump to list + if (e.Key == Key.CursorDown && search.HasFocus && listview.SelectedItem == 0 && searchset.Count > 0) { // jump to list this.SetFocus (listview); SetValue (searchset [listview.SelectedItem]); return true; } - if (e.Key == Key.CursorUp && listview.HasFocus && listview.SelectedItem == 0) // jump back to search + if (e.Key == Key.CursorUp && listview.HasFocus && listview.SelectedItem == 0 && searchset.Count > 0) // jump back to search { search.CursorPosition = search.Text.Length; this.SetFocus (search); From 2cf92c096e686b7cf86a9e6ee77a7da7404ee2b6 Mon Sep 17 00:00:00 2001 From: Ross Ferguson Date: Fri, 22 May 2020 09:20:48 +0100 Subject: [PATCH 21/24] Add Scenario to UI Catalog for ListView and ComboBox --- UICatalog/Scenarios/ListsAndCombos.cs | 57 +++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) create mode 100644 UICatalog/Scenarios/ListsAndCombos.cs diff --git a/UICatalog/Scenarios/ListsAndCombos.cs b/UICatalog/Scenarios/ListsAndCombos.cs new file mode 100644 index 000000000..eb5682d70 --- /dev/null +++ b/UICatalog/Scenarios/ListsAndCombos.cs @@ -0,0 +1,57 @@ +using System; +using System.Linq; +using System.IO; +using System.Collections.Generic; +using Terminal.Gui; +using NStack; + +namespace UICatalog.Scenarios { + [ScenarioMetadata (Name: "Lists", Description: "Demonstrates list selections")] + [ScenarioCategory ("Controls")] + class ListsAndCombos : Scenario { + + public override void Setup () + { + List items = new List (); + foreach (var dir in new [] { "/etc", @"\windows\System32" }) { + if (Directory.Exists (dir)) { + items = Directory.GetFiles (dir) + .Select (Path.GetFileName) + .Where (x => char.IsLetterOrDigit (x [0])) + .Distinct () + .OrderBy (x => x).ToList (); + } + } + + // ListView + var lbListView = new Label ("Listview") { + ColorScheme = Colors.TopLevel, + X = 0, + Width = 30 + }; + + var listview = new ListView (items) { + X = 0, + Y = Pos.Bottom (lbListView) + 1, + Width = 30 + }; + listview.OpenSelectedItem += (object sender, EventArgs text) => lbListView.Text = items [listview.SelectedItem]; + Win.Add (lbListView, listview); + + // ComboBox + var lbComboBox = new Label ("ComboBox") { + ColorScheme = Colors.TopLevel, + X = Pos.Right (lbListView) + 1, // <== Broken?!? + Width = 30 + }; + + var comboBox = new ComboBox (0, 0, 30, 10, items) { + X = Pos.Right(listview) + 1 , + Y = Pos.Bottom (lbListView) +1, + Width = 30 + }; + comboBox.Changed += (object sender, ustring text) => lbComboBox.Text = text; + Win.Add (lbComboBox, comboBox); + } + } +} From c922017c4f82bf595df1d04146ed893c392ee541 Mon Sep 17 00:00:00 2001 From: Ross Ferguson Date: Fri, 22 May 2020 10:43:05 +0100 Subject: [PATCH 22/24] *Merge* and fix build. (Rebase attempt was disaster) --- Terminal.Gui/Views/ComboBox.cs | 4 ++-- UICatalog/Scenarios/ListsAndCombos.cs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Terminal.Gui/Views/ComboBox.cs b/Terminal.Gui/Views/ComboBox.cs index b04d6dbc3..11e3c3aff 100644 --- a/Terminal.Gui/Views/ComboBox.cs +++ b/Terminal.Gui/Views/ComboBox.cs @@ -54,12 +54,12 @@ namespace Terminal.Gui { LayoutStyle = LayoutStyle.Computed, ColorScheme = Colors.Dialog }; - listview.SelectedChanged += () => { + listview.SelectedChanged += (object sender, ListViewItemEventArgs e) => { if(searchset.Count > 0) SetValue (searchset [listview.SelectedItem]); }; - Application.OnLoad += () => { + Application.Loaded += (object sender, Application.ResizedEventArgs e) => { // Determine if this view is hosted inside a dialog for (View view = this.SuperView; view != null; view = view.SuperView) { if (view is Dialog) { diff --git a/UICatalog/Scenarios/ListsAndCombos.cs b/UICatalog/Scenarios/ListsAndCombos.cs index eb5682d70..31daec9c3 100644 --- a/UICatalog/Scenarios/ListsAndCombos.cs +++ b/UICatalog/Scenarios/ListsAndCombos.cs @@ -35,7 +35,7 @@ namespace UICatalog.Scenarios { Y = Pos.Bottom (lbListView) + 1, Width = 30 }; - listview.OpenSelectedItem += (object sender, EventArgs text) => lbListView.Text = items [listview.SelectedItem]; + listview.OpenSelectedItem += (object sender, ListViewItemEventArgs e) => lbListView.Text = items [listview.SelectedItem]; Win.Add (lbListView, listview); // ComboBox From b76dcac9c019db24da9a99e13fc0632c547bf222 Mon Sep 17 00:00:00 2001 From: Ross Ferguson Date: Sat, 23 May 2020 00:21:30 +0100 Subject: [PATCH 23/24] Differentiate list color from search --- Terminal.Gui/Views/ComboBox.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Terminal.Gui/Views/ComboBox.cs b/Terminal.Gui/Views/ComboBox.cs index 11e3c3aff..71657731c 100644 --- a/Terminal.Gui/Views/ComboBox.cs +++ b/Terminal.Gui/Views/ComboBox.cs @@ -52,7 +52,7 @@ namespace Terminal.Gui { listview = new ListView(new Rect(x, y + 1, w, 0), listsource.ToList()) { LayoutStyle = LayoutStyle.Computed, - ColorScheme = Colors.Dialog + ColorScheme = Colors.Menu }; listview.SelectedChanged += (object sender, ListViewItemEventArgs e) => { if(searchset.Count > 0) @@ -109,6 +109,7 @@ namespace Terminal.Gui { SetValue( searchset [listview.SelectedItem]); search.CursorPosition = search.Text.Length; + Search_Changed (search, search.Text); Changed?.Invoke (this, text); searchset.Clear(); From bc5148623681bbcf2b0e860637d2b6ff44a5acdf Mon Sep 17 00:00:00 2001 From: Ross Ferguson Date: Sat, 23 May 2020 09:07:08 +0100 Subject: [PATCH 24/24] List narrower than the edit when in Window Add XML comments --- Terminal.Gui/Views/ComboBox.cs | 23 ++++++++++++++++++----- UICatalog/Scenarios/ListsAndCombos.cs | 2 +- 2 files changed, 19 insertions(+), 6 deletions(-) diff --git a/Terminal.Gui/Views/ComboBox.cs b/Terminal.Gui/Views/ComboBox.cs index 71657731c..627ec6fc7 100644 --- a/Terminal.Gui/Views/ComboBox.cs +++ b/Terminal.Gui/Views/ComboBox.cs @@ -52,7 +52,6 @@ namespace Terminal.Gui { listview = new ListView(new Rect(x, y + 1, w, 0), listsource.ToList()) { LayoutStyle = LayoutStyle.Computed, - ColorScheme = Colors.Menu }; listview.SelectedChanged += (object sender, ListViewItemEventArgs e) => { if(searchset.Count > 0) @@ -73,8 +72,13 @@ namespace Terminal.Gui { // Needs to be re-applied for LayoutStyle.Computed listview.X = x; listview.Y = y + 1; - listview.Width = w; + listview.Width = CalculateWidth(); listview.Height = CalculatetHeight (); + + if (autoHide) + listview.ColorScheme = Colors.Menu; + else + search.ColorScheme = Colors.Menu; }; this.Add(listview); @@ -82,6 +86,7 @@ namespace Terminal.Gui { this.SetFocus(search); } + /// public override bool OnEnter () { if (!search.HasFocus) @@ -92,6 +97,7 @@ namespace Terminal.Gui { return true; } + /// public override bool ProcessKey(KeyEvent e) { if (e.Key == Key.Tab) @@ -101,7 +107,6 @@ namespace Terminal.Gui { } if (e.Key == Key.Enter && listview.HasFocus) { - if (listview.Source.Count == 0 || searchset.Count == 0) { text = ""; return true; @@ -189,9 +194,8 @@ namespace Terminal.Gui { private void Search_Changed (object sender, ustring text) { - if (string.IsNullOrEmpty (search.Text.ToString())) { + if (string.IsNullOrEmpty (search.Text.ToString())) searchset = autoHide ? new List () : listsource; - } else searchset = listsource.Where (x => x.StartsWith (search.Text.ToString (), StringComparison.CurrentCultureIgnoreCase)).ToList (); @@ -210,5 +214,14 @@ namespace Terminal.Gui { { return Math.Min (height, searchset.Count); } + + /// + /// Internal width + /// + /// + private int CalculateWidth() + { + return autoHide? Math.Max (1, width - 1) : width; + } } } diff --git a/UICatalog/Scenarios/ListsAndCombos.cs b/UICatalog/Scenarios/ListsAndCombos.cs index 31daec9c3..f5633e47b 100644 --- a/UICatalog/Scenarios/ListsAndCombos.cs +++ b/UICatalog/Scenarios/ListsAndCombos.cs @@ -41,7 +41,7 @@ namespace UICatalog.Scenarios { // ComboBox var lbComboBox = new Label ("ComboBox") { ColorScheme = Colors.TopLevel, - X = Pos.Right (lbListView) + 1, // <== Broken?!? + X = Pos.Right (lbListView) + 1, Width = 30 };