TimeField.cs 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253
  1. //
  2. // TimeField.cs: text entry for time
  3. //
  4. // Author: Jörg Preiß
  5. //
  6. // Licensed under the MIT license
  7. using System;
  8. using System.Globalization;
  9. using System.Linq;
  10. using NStack;
  11. namespace Terminal.Gui {
  12. /// <summary>
  13. /// Time editing <see cref="View"/>
  14. /// </summary>
  15. /// <remarks>
  16. /// The <see cref="TimeField"/> <see cref="View"/> provides time editing functionality with mouse support.
  17. /// </remarks>
  18. public class TimeField : TextField {
  19. bool isShort;
  20. int longFieldLen = 8;
  21. int shortFieldLen = 5;
  22. string sepChar;
  23. string longFormat;
  24. string shortFormat;
  25. int FieldLen { get { return isShort ? shortFieldLen : longFieldLen; } }
  26. string Format { get { return isShort ? shortFormat : longFormat; } }
  27. /// <summary>
  28. /// Initializes a new instance of <see cref="TimeField"/> using <see cref="LayoutStyle.Absolute"/> positioning.
  29. /// </summary>
  30. /// <param name="x">The x coordinate.</param>
  31. /// <param name="y">The y coordinate.</param>
  32. /// <param name="time">Initial time.</param>
  33. /// <param name="isShort">If true, the seconds are hidden. Sets the <see cref="IsShortFormat"/> property.</param>
  34. public TimeField (int x, int y, DateTime time, bool isShort = false) : base (x, y, isShort ? 7 : 10, "")
  35. {
  36. this.isShort = isShort;
  37. Initialize (time);
  38. }
  39. /// <summary>
  40. /// Initializes a new instance of <see cref="TimeField"/> using <see cref="LayoutStyle.Computed"/> positioning.
  41. /// </summary>
  42. /// <param name="time">Initial time</param>
  43. public TimeField (DateTime time) : base (string.Empty)
  44. {
  45. this.isShort = true;
  46. Width = FieldLen + 2;
  47. Initialize (time);
  48. }
  49. /// <summary>
  50. /// Initializes a new instance of <see cref="TimeField"/> using <see cref="LayoutStyle.Computed"/> positioning.
  51. /// </summary>
  52. public TimeField () : this (time: DateTime.MinValue) { }
  53. void Initialize (DateTime time)
  54. {
  55. CultureInfo cultureInfo = CultureInfo.CurrentCulture;
  56. sepChar = cultureInfo.DateTimeFormat.TimeSeparator;
  57. longFormat = $" HH{sepChar}mm{sepChar}ss";
  58. shortFormat = $" HH{sepChar}mm";
  59. CursorPosition = 1;
  60. Time = time;
  61. Changed += TimeField_Changed;
  62. }
  63. void TimeField_Changed (object sender, ustring e)
  64. {
  65. if (!DateTime.TryParseExact (Text.ToString (), Format, CultureInfo.CurrentCulture, DateTimeStyles.None, out DateTime result))
  66. Text = e;
  67. }
  68. /// <summary>
  69. /// Gets or sets the time of the <see cref="TimeField"/>.
  70. /// </summary>
  71. /// <remarks>
  72. /// </remarks>
  73. public DateTime Time {
  74. get {
  75. if (!DateTime.TryParseExact (Text.ToString (), Format, CultureInfo.CurrentCulture, DateTimeStyles.None, out DateTime result)) return new DateTime ();
  76. return result;
  77. }
  78. set {
  79. this.Text = value.ToString (Format);
  80. }
  81. }
  82. /// <summary>
  83. /// Get or sets whether <see cref="TimeField"/> uses the short or long time format.
  84. /// </summary>
  85. public bool IsShortFormat {
  86. get => isShort;
  87. set {
  88. isShort = value;
  89. if (isShort)
  90. Width = 7;
  91. else
  92. Width = 10;
  93. var ro = ReadOnly;
  94. if (ro)
  95. ReadOnly = false;
  96. SetText (Text);
  97. ReadOnly = ro;
  98. SetNeedsDisplay ();
  99. }
  100. }
  101. bool SetText (Rune key)
  102. {
  103. var text = TextModel.ToRunes (Text);
  104. var newText = text.GetRange (0, CursorPosition);
  105. newText.Add (key);
  106. if (CursorPosition < FieldLen)
  107. newText = newText.Concat (text.GetRange (CursorPosition + 1, text.Count - (CursorPosition + 1))).ToList ();
  108. return SetText (ustring.Make (newText));
  109. }
  110. bool SetText (ustring text)
  111. {
  112. ustring [] vals = text.Split (ustring.Make (sepChar));
  113. bool isValidTime = true;
  114. int hour = Int32.Parse (vals [0].ToString ());
  115. int minute = Int32.Parse (vals [1].ToString ());
  116. int second = isShort ? 0 : vals.Length > 2 ? Int32.Parse (vals [2].ToString ()) : 0;
  117. if (hour < 0) {
  118. isValidTime = false;
  119. hour = 0;
  120. vals [0] = "0";
  121. } else if (hour > 23) {
  122. isValidTime = false;
  123. hour = 23;
  124. vals [0] = "23";
  125. }
  126. if (minute < 0) {
  127. isValidTime = false;
  128. minute = 0;
  129. vals [1] = "0";
  130. } else if (minute > 59) {
  131. isValidTime = false;
  132. minute = 59;
  133. vals [1] = "59";
  134. }
  135. if (second < 0) {
  136. isValidTime = false;
  137. second = 0;
  138. vals [2] = "0";
  139. } else if (second > 59) {
  140. isValidTime = false;
  141. second = 59;
  142. vals [2] = "59";
  143. }
  144. string time = isShort ? $" {hour,2:00}{sepChar}{minute,2:00}" : $" {hour,2:00}{sepChar}{minute,2:00}{sepChar}{second,2:00}";
  145. Text = time;
  146. if (!DateTime.TryParseExact (text.ToString (), Format, CultureInfo.CurrentCulture, DateTimeStyles.None, out DateTime result) ||
  147. !isValidTime)
  148. return false;
  149. return true;
  150. }
  151. void IncCursorPosition ()
  152. {
  153. if (CursorPosition == FieldLen)
  154. return;
  155. if (Text [++CursorPosition] == sepChar.ToCharArray () [0])
  156. CursorPosition++;
  157. }
  158. void DecCursorPosition ()
  159. {
  160. if (CursorPosition == 1)
  161. return;
  162. if (Text [--CursorPosition] == sepChar.ToCharArray () [0])
  163. CursorPosition--;
  164. }
  165. void AdjCursorPosition ()
  166. {
  167. if (Text [CursorPosition] == sepChar.ToCharArray () [0])
  168. CursorPosition++;
  169. }
  170. ///<inheritdoc/>
  171. public override bool ProcessKey (KeyEvent kb)
  172. {
  173. switch (kb.Key) {
  174. case Key.DeleteChar:
  175. case Key.ControlD:
  176. SetText ('0');
  177. break;
  178. case Key.Delete:
  179. case Key.Backspace:
  180. SetText ('0');
  181. DecCursorPosition ();
  182. break;
  183. // Home, C-A
  184. case Key.Home:
  185. case Key.ControlA:
  186. CursorPosition = 1;
  187. break;
  188. case Key.CursorLeft:
  189. case Key.ControlB:
  190. DecCursorPosition ();
  191. break;
  192. case Key.End:
  193. case Key.ControlE: // End
  194. CursorPosition = FieldLen;
  195. break;
  196. case Key.CursorRight:
  197. case Key.ControlF:
  198. IncCursorPosition ();
  199. break;
  200. default:
  201. // Ignore non-numeric characters.
  202. if (kb.Key < (Key)((int)'0') || kb.Key > (Key)((int)'9'))
  203. return false;
  204. if (SetText (TextModel.ToRunes (ustring.Make ((uint)kb.Key)).First ()))
  205. IncCursorPosition ();
  206. return true;
  207. }
  208. return true;
  209. }
  210. ///<inheritdoc/>
  211. public override bool MouseEvent (MouseEvent ev)
  212. {
  213. if (!ev.Flags.HasFlag (MouseFlags.Button1Clicked))
  214. return false;
  215. if (!HasFocus)
  216. SuperView.SetFocus (this);
  217. var point = ev.X;
  218. if (point > FieldLen)
  219. point = FieldLen;
  220. if (point < 1)
  221. point = 1;
  222. CursorPosition = point;
  223. AdjCursorPosition ();
  224. return true;
  225. }
  226. }
  227. }