TimeField.cs 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299
  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. TimeSpan time;
  20. bool isShort;
  21. int longFieldLen = 8;
  22. int shortFieldLen = 5;
  23. string sepChar;
  24. string longFormat;
  25. string shortFormat;
  26. int FieldLen { get { return isShort ? shortFieldLen : longFieldLen; } }
  27. string Format { get { return isShort ? shortFormat : longFormat; } }
  28. /// <summary>
  29. /// TimeChanged event, raised when the Date has changed.
  30. /// </summary>
  31. /// <remarks>
  32. /// This event is raised when the <see cref="Time"/> changes.
  33. /// </remarks>
  34. /// <remarks>
  35. /// The passed <see cref="EventArgs"/> is a <see cref="DateTimeEventArgs{T}"/> containing the old value, new value, and format string.
  36. /// </remarks>
  37. public event Action<DateTimeEventArgs<TimeSpan>> TimeChanged;
  38. /// <summary>
  39. /// Initializes a new instance of <see cref="TimeField"/> using <see cref="LayoutStyle.Absolute"/> positioning.
  40. /// </summary>
  41. /// <param name="x">The x coordinate.</param>
  42. /// <param name="y">The y coordinate.</param>
  43. /// <param name="time">Initial time.</param>
  44. /// <param name="isShort">If true, the seconds are hidden. Sets the <see cref="IsShortFormat"/> property.</param>
  45. public TimeField (int x, int y, TimeSpan time, bool isShort = false) : base (x, y, isShort ? 7 : 10, "")
  46. {
  47. this.isShort = isShort;
  48. Initialize (time);
  49. }
  50. /// <summary>
  51. /// Initializes a new instance of <see cref="TimeField"/> using <see cref="LayoutStyle.Computed"/> positioning.
  52. /// </summary>
  53. /// <param name="time">Initial time</param>
  54. public TimeField (TimeSpan time) : base (string.Empty)
  55. {
  56. this.isShort = true;
  57. Width = FieldLen + 2;
  58. Initialize (time);
  59. }
  60. /// <summary>
  61. /// Initializes a new instance of <see cref="TimeField"/> using <see cref="LayoutStyle.Computed"/> positioning.
  62. /// </summary>
  63. public TimeField () : this (time: TimeSpan.MinValue) { }
  64. void Initialize (TimeSpan time)
  65. {
  66. CultureInfo cultureInfo = CultureInfo.CurrentCulture;
  67. sepChar = cultureInfo.DateTimeFormat.TimeSeparator;
  68. longFormat = $" hh\\{sepChar}mm\\{sepChar}ss";
  69. shortFormat = $" hh\\{sepChar}mm";
  70. CursorPosition = 1;
  71. Time = time;
  72. TextChanged += TextField_TextChanged;
  73. }
  74. void TextField_TextChanged (ustring e)
  75. {
  76. try {
  77. if (!TimeSpan.TryParseExact (Text.ToString ().Trim (), Format.Trim (), CultureInfo.CurrentCulture, TimeSpanStyles.None, out TimeSpan result))
  78. Text = e;
  79. } catch (Exception) {
  80. Text = e;
  81. }
  82. }
  83. /// <summary>
  84. /// Gets or sets the time of the <see cref="TimeField"/>.
  85. /// </summary>
  86. /// <remarks>
  87. /// </remarks>
  88. public TimeSpan Time {
  89. get {
  90. return time;
  91. }
  92. set {
  93. if (ReadOnly)
  94. return;
  95. var oldTime = time;
  96. time = value;
  97. this.Text = " " + value.ToString (Format.Trim ());
  98. var args = new DateTimeEventArgs<TimeSpan> (oldTime, value, Format);
  99. if (oldTime != value) {
  100. OnTimeChanged (args);
  101. }
  102. }
  103. }
  104. /// <summary>
  105. /// Get or sets whether <see cref="TimeField"/> uses the short or long time format.
  106. /// </summary>
  107. public bool IsShortFormat {
  108. get => isShort;
  109. set {
  110. isShort = value;
  111. if (isShort)
  112. Width = 7;
  113. else
  114. Width = 10;
  115. var ro = ReadOnly;
  116. if (ro)
  117. ReadOnly = false;
  118. SetText (Text);
  119. ReadOnly = ro;
  120. SetNeedsDisplay ();
  121. }
  122. }
  123. bool SetText (Rune key)
  124. {
  125. var text = TextModel.ToRunes (Text);
  126. var newText = text.GetRange (0, CursorPosition);
  127. newText.Add (key);
  128. if (CursorPosition < FieldLen)
  129. newText = newText.Concat (text.GetRange (CursorPosition + 1, text.Count - (CursorPosition + 1))).ToList ();
  130. return SetText (ustring.Make (newText));
  131. }
  132. bool SetText (ustring text)
  133. {
  134. if (text.IsEmpty) {
  135. return false;
  136. }
  137. ustring [] vals = text.Split (ustring.Make (sepChar));
  138. bool isValidTime = true;
  139. int hour = Int32.Parse (vals [0].ToString ());
  140. int minute = Int32.Parse (vals [1].ToString ());
  141. int second = isShort ? 0 : vals.Length > 2 ? Int32.Parse (vals [2].ToString ()) : 0;
  142. if (hour < 0) {
  143. isValidTime = false;
  144. hour = 0;
  145. vals [0] = "0";
  146. } else if (hour > 23) {
  147. isValidTime = false;
  148. hour = 23;
  149. vals [0] = "23";
  150. }
  151. if (minute < 0) {
  152. isValidTime = false;
  153. minute = 0;
  154. vals [1] = "0";
  155. } else if (minute > 59) {
  156. isValidTime = false;
  157. minute = 59;
  158. vals [1] = "59";
  159. }
  160. if (second < 0) {
  161. isValidTime = false;
  162. second = 0;
  163. vals [2] = "0";
  164. } else if (second > 59) {
  165. isValidTime = false;
  166. second = 59;
  167. vals [2] = "59";
  168. }
  169. string t = isShort ? $" {hour,2:00}{sepChar}{minute,2:00}" : $" {hour,2:00}{sepChar}{minute,2:00}{sepChar}{second,2:00}";
  170. if (!TimeSpan.TryParseExact (t.Trim (), Format.Trim (), CultureInfo.CurrentCulture, TimeSpanStyles.None, out TimeSpan result) ||
  171. !isValidTime)
  172. return false;
  173. Time = result;
  174. return true;
  175. }
  176. void IncCursorPosition ()
  177. {
  178. if (CursorPosition == FieldLen)
  179. return;
  180. if (Text [++CursorPosition] == sepChar.ToCharArray () [0])
  181. CursorPosition++;
  182. }
  183. void DecCursorPosition ()
  184. {
  185. if (CursorPosition == 1)
  186. return;
  187. if (Text [--CursorPosition] == sepChar.ToCharArray () [0])
  188. CursorPosition--;
  189. }
  190. void AdjCursorPosition ()
  191. {
  192. if (Text [CursorPosition] == sepChar.ToCharArray () [0])
  193. CursorPosition++;
  194. }
  195. ///<inheritdoc/>
  196. public override bool ProcessKey (KeyEvent kb)
  197. {
  198. switch (kb.Key) {
  199. case Key.DeleteChar:
  200. case Key.D | Key.CtrlMask:
  201. if (ReadOnly)
  202. return true;
  203. SetText ('0');
  204. break;
  205. case Key.Delete:
  206. case Key.Backspace:
  207. if (ReadOnly)
  208. return true;
  209. SetText ('0');
  210. DecCursorPosition ();
  211. break;
  212. // Home, C-A
  213. case Key.Home:
  214. case Key.A | Key.CtrlMask:
  215. CursorPosition = 1;
  216. break;
  217. case Key.CursorLeft:
  218. case Key.B | Key.CtrlMask:
  219. DecCursorPosition ();
  220. break;
  221. case Key.End:
  222. case Key.E | Key.CtrlMask: // End
  223. CursorPosition = FieldLen;
  224. break;
  225. case Key.CursorRight:
  226. case Key.F | Key.CtrlMask:
  227. IncCursorPosition ();
  228. break;
  229. default:
  230. // Ignore non-numeric characters.
  231. if (kb.Key < (Key)((int)Key.D0) || kb.Key > (Key)((int)Key.D9))
  232. return false;
  233. if (ReadOnly)
  234. return true;
  235. if (SetText (TextModel.ToRunes (ustring.Make ((uint)kb.Key)).First ()))
  236. IncCursorPosition ();
  237. return true;
  238. }
  239. return true;
  240. }
  241. ///<inheritdoc/>
  242. public override bool MouseEvent (MouseEvent ev)
  243. {
  244. if (!ev.Flags.HasFlag (MouseFlags.Button1Clicked))
  245. return false;
  246. if (!HasFocus)
  247. SetFocus ();
  248. var point = ev.X;
  249. if (point > FieldLen)
  250. point = FieldLen;
  251. if (point < 1)
  252. point = 1;
  253. CursorPosition = point;
  254. AdjCursorPosition ();
  255. return true;
  256. }
  257. /// <summary>
  258. /// Event firing method that invokes the <see cref="TimeChanged"/> event.
  259. /// </summary>
  260. /// <param name="args">The event arguments</param>
  261. public virtual void OnTimeChanged (DateTimeEventArgs<TimeSpan> args)
  262. {
  263. TimeChanged?.Invoke (args);
  264. }
  265. }
  266. }