TextFieldAutoComplete.cs 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199
  1. //
  2. // TextFieldAutoComplete.cs: TextField with AutoComplete
  3. //
  4. // Author:
  5. // Ross Ferguson ([email protected])
  6. //
  7. // TODO:
  8. // * Completion list auto appears when hosted directly in a Window as opposed to a dialog
  9. //
  10. using System;
  11. using System.Linq;
  12. using System.Collections.Generic;
  13. using NStack;
  14. namespace Terminal.Gui {
  15. /// <summary>
  16. /// TextField with AutoComplete
  17. /// </summary>
  18. public class TextFieldAutoComplete : View {
  19. /// <summary>
  20. /// Changed event, raised when the selection has been confirmed.
  21. /// </summary>
  22. /// <remarks>
  23. /// Client code can hook up to this event, it is
  24. /// raised when the selection has been confirmed.
  25. /// </remarks>
  26. public event EventHandler<ustring> Changed;
  27. readonly IList<string> listsource;
  28. IList<string> searchset;
  29. ustring text = "";
  30. readonly TextField search;
  31. readonly ListView listview;
  32. readonly int height;
  33. readonly int width;
  34. /// <summary>
  35. /// Public constructor
  36. /// </summary>
  37. /// <param name="x">The x coordinate</param>
  38. /// <param name="y">The y coordinate</param>
  39. /// <param name="w">The width</param>
  40. /// <param name="h">The height</param>
  41. /// <param name="source">Auto completetion source</param>
  42. public TextFieldAutoComplete(int x, int y, int w, int h, IList<string> source)
  43. {
  44. listsource = searchset = source;
  45. height = h;
  46. width = w;
  47. search = new TextField(x, y, w, "");
  48. search.Changed += Search_Changed;
  49. listview = new ListView(new Rect(x, y + 1, w, CalculatetHeight()), listsource.ToList())
  50. {
  51. LayoutStyle = LayoutStyle.Computed,
  52. ColorScheme = Colors.Dialog
  53. };
  54. // Needs to be re-applied for LayoutStyle.Computed
  55. listview.X = x;
  56. listview.Y = y + 1;
  57. listview.Width = w;
  58. listview.Height = CalculatetHeight ();
  59. this.Add(listview);
  60. this.Add(search);
  61. this.SetFocus(search);
  62. this.OnEnter += (object sender, EventArgs e) => {
  63. this.SetFocus(search);
  64. search.CursorPosition = search.Text.Length;
  65. };
  66. }
  67. public override bool ProcessKey(KeyEvent e)
  68. {
  69. if (e.Key == Key.Tab)
  70. {
  71. base.ProcessKey(e);
  72. return false; // allow tab-out to next control
  73. }
  74. if (e.Key == Key.Enter && listview.HasFocus) {
  75. if (listview.Source.Count == 0 || searchset.Count == 0) {
  76. text = "";
  77. return true;
  78. }
  79. search.Text = text = searchset [listview.SelectedItem];
  80. search.CursorPosition = search.Text.Length;
  81. Changed?.Invoke (this, text);
  82. searchset.Clear();
  83. listview.Clear();
  84. this.SetFocus(search);
  85. return true;
  86. }
  87. if (e.Key == Key.CursorDown && search.HasFocus) // jump to list
  88. {
  89. this.SetFocus(listview);
  90. listview.SelectedItem = 0;
  91. return true;
  92. }
  93. if(e.Key == Key.CursorUp && listview.SelectedItem == 0 && listview.HasFocus) // jump back to search
  94. {
  95. this.SetFocus(search);
  96. return true;
  97. }
  98. if (e.Key == Key.Esc) {
  99. this.SetFocus (search);
  100. search.Text = text = "";
  101. Changed?.Invoke (this, search.Text);
  102. return true;
  103. }
  104. // Unix emulation
  105. if (e.Key == Key.ControlU)
  106. {
  107. Reset();
  108. return true;
  109. }
  110. return base.ProcessKey(e);
  111. }
  112. /// <summary>
  113. /// The currenlty selected list item
  114. /// </summary>
  115. public ustring Text
  116. {
  117. get
  118. {
  119. return text;
  120. }
  121. set {
  122. search.Text = text = value;
  123. }
  124. }
  125. /// <summary>
  126. /// Reset to full original list
  127. /// </summary>
  128. private void Reset()
  129. {
  130. search.Text = text = "";
  131. Changed?.Invoke (this, search.Text);
  132. searchset = listsource;
  133. listview.SetSource(searchset.ToList());
  134. listview.Height = CalculatetHeight ();
  135. listview.Redraw (new Rect (0, 0, width, height));
  136. this.SetFocus(search);
  137. }
  138. private void Search_Changed (object sender, ustring text)
  139. {
  140. // Cannot use text argument as its old value (pre-change)
  141. if (string.IsNullOrEmpty (search.Text.ToString())) {
  142. searchset = listsource;
  143. }
  144. else
  145. searchset = listsource.Where (x => x.StartsWith (search.Text.ToString (), StringComparison.CurrentCultureIgnoreCase)).ToList ();
  146. listview.SetSource (searchset.ToList ());
  147. listview.Height = CalculatetHeight ();
  148. listview.Redraw (new Rect (0, 0, width, height));
  149. }
  150. /// <summary>
  151. /// Internal height of dynamic search list
  152. /// </summary>
  153. /// <returns></returns>
  154. private int CalculatetHeight ()
  155. {
  156. return Math.Min (height, searchset.Count);
  157. }
  158. /// <summary>
  159. /// Determine if this view is hosted inside a dialog
  160. /// </summary>
  161. /// <returns></returns>
  162. private bool IsDialogHosted()
  163. {
  164. for (View v = this.SuperView; v != null; v = v.SuperView) {
  165. if (v.GetType () == typeof (Dialog))
  166. return true;
  167. }
  168. return false;
  169. }
  170. }
  171. }