TextFieldAutoComplete.cs 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203
  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. }
  63. public override bool OnEnter ()
  64. {
  65. if (!search.HasFocus)
  66. this.SetFocus (search);
  67. search.CursorPosition = search.Text.Length;
  68. return true;
  69. }
  70. public override bool ProcessKey(KeyEvent e)
  71. {
  72. if (e.Key == Key.Tab)
  73. {
  74. base.ProcessKey(e);
  75. return false; // allow tab-out to next control
  76. }
  77. if (e.Key == Key.Enter && listview.HasFocus) {
  78. if (listview.Source.Count == 0 || searchset.Count == 0) {
  79. text = "";
  80. return true;
  81. }
  82. search.Text = text = searchset [listview.SelectedItem];
  83. search.CursorPosition = search.Text.Length;
  84. Changed?.Invoke (this, text);
  85. searchset.Clear();
  86. listview.Clear();
  87. this.SetFocus(search);
  88. return true;
  89. }
  90. if (e.Key == Key.CursorDown && search.HasFocus) // jump to list
  91. {
  92. this.SetFocus(listview);
  93. listview.SelectedItem = 0;
  94. return true;
  95. }
  96. if(e.Key == Key.CursorUp && listview.SelectedItem == 0 && listview.HasFocus) // jump back to search
  97. {
  98. this.SetFocus(search);
  99. return true;
  100. }
  101. if (e.Key == Key.Esc) {
  102. this.SetFocus (search);
  103. search.Text = text = "";
  104. Changed?.Invoke (this, search.Text);
  105. return true;
  106. }
  107. // Unix emulation
  108. if (e.Key == Key.ControlU)
  109. {
  110. Reset();
  111. return true;
  112. }
  113. return base.ProcessKey(e);
  114. }
  115. /// <summary>
  116. /// The currenlty selected list item
  117. /// </summary>
  118. public ustring Text
  119. {
  120. get
  121. {
  122. return text;
  123. }
  124. set {
  125. search.Text = text = value;
  126. }
  127. }
  128. /// <summary>
  129. /// Reset to full original list
  130. /// </summary>
  131. private void Reset()
  132. {
  133. search.Text = text = "";
  134. Changed?.Invoke (this, search.Text);
  135. searchset = listsource;
  136. listview.SetSource(searchset.ToList());
  137. listview.Height = CalculatetHeight ();
  138. listview.Redraw (new Rect (0, 0, width, height));
  139. this.SetFocus(search);
  140. }
  141. private void Search_Changed (object sender, ustring text)
  142. {
  143. // Cannot use text argument as its old value (pre-change)
  144. if (string.IsNullOrEmpty (search.Text.ToString())) {
  145. searchset = listsource;
  146. }
  147. else
  148. searchset = listsource.Where (x => x.StartsWith (search.Text.ToString (), StringComparison.CurrentCultureIgnoreCase)).ToList ();
  149. listview.SetSource (searchset.ToList ());
  150. listview.Height = CalculatetHeight ();
  151. listview.Redraw (new Rect (0, 0, width, height));
  152. }
  153. /// <summary>
  154. /// Internal height of dynamic search list
  155. /// </summary>
  156. /// <returns></returns>
  157. private int CalculatetHeight ()
  158. {
  159. return Math.Min (height, searchset.Count);
  160. }
  161. /// <summary>
  162. /// Determine if this view is hosted inside a dialog
  163. /// </summary>
  164. /// <returns></returns>
  165. private bool IsDialogHosted()
  166. {
  167. for (View v = this.SuperView; v != null; v = v.SuperView) {
  168. if (v.GetType () == typeof (Dialog))
  169. return true;
  170. }
  171. return false;
  172. }
  173. }
  174. }