TimeField.cs 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300
  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. DateTime 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"/> containing the old, new value and format.
  36. /// </remarks>
  37. public event EventHandler<DateTimeEventArgs> 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, DateTime 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 (DateTime 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: DateTime.MinValue) { }
  64. void Initialize (DateTime 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. this.time = time;
  72. Text = time.ToString (Format);
  73. Changed += TimeField_Changed;
  74. }
  75. void TimeField_Changed (object sender, ustring e)
  76. {
  77. try {
  78. if (!DateTime.TryParseExact (Text.ToString (), Format, CultureInfo.CurrentCulture, DateTimeStyles.None, out DateTime result))
  79. Text = e;
  80. } catch (Exception) {
  81. Text = e;
  82. }
  83. }
  84. /// <summary>
  85. /// Gets or sets the time of the <see cref="TimeField"/>.
  86. /// </summary>
  87. /// <remarks>
  88. /// </remarks>
  89. public DateTime Time {
  90. get {
  91. return time;
  92. }
  93. set {
  94. if (ReadOnly)
  95. return;
  96. var oldTime = time;
  97. time = value;
  98. this.Text = value.ToString (Format);
  99. var args = new DateTimeEventArgs (oldTime, value, Format);
  100. if (oldTime != value) {
  101. OnTimeChanged (args);
  102. }
  103. }
  104. }
  105. /// <summary>
  106. /// Get or sets whether <see cref="TimeField"/> uses the short or long time format.
  107. /// </summary>
  108. public bool IsShortFormat {
  109. get => isShort;
  110. set {
  111. isShort = value;
  112. if (isShort)
  113. Width = 7;
  114. else
  115. Width = 10;
  116. var ro = ReadOnly;
  117. if (ro)
  118. ReadOnly = false;
  119. SetText (Text);
  120. ReadOnly = ro;
  121. SetNeedsDisplay ();
  122. }
  123. }
  124. bool SetText (Rune key)
  125. {
  126. var text = TextModel.ToRunes (Text);
  127. var newText = text.GetRange (0, CursorPosition);
  128. newText.Add (key);
  129. if (CursorPosition < FieldLen)
  130. newText = newText.Concat (text.GetRange (CursorPosition + 1, text.Count - (CursorPosition + 1))).ToList ();
  131. return SetText (ustring.Make (newText));
  132. }
  133. bool SetText (ustring text)
  134. {
  135. if (text.IsEmpty) {
  136. return false;
  137. }
  138. ustring [] vals = text.Split (ustring.Make (sepChar));
  139. bool isValidTime = true;
  140. int hour = Int32.Parse (vals [0].ToString ());
  141. int minute = Int32.Parse (vals [1].ToString ());
  142. int second = isShort ? 0 : vals.Length > 2 ? Int32.Parse (vals [2].ToString ()) : 0;
  143. if (hour < 0) {
  144. isValidTime = false;
  145. hour = 0;
  146. vals [0] = "0";
  147. } else if (hour > 23) {
  148. isValidTime = false;
  149. hour = 23;
  150. vals [0] = "23";
  151. }
  152. if (minute < 0) {
  153. isValidTime = false;
  154. minute = 0;
  155. vals [1] = "0";
  156. } else if (minute > 59) {
  157. isValidTime = false;
  158. minute = 59;
  159. vals [1] = "59";
  160. }
  161. if (second < 0) {
  162. isValidTime = false;
  163. second = 0;
  164. vals [2] = "0";
  165. } else if (second > 59) {
  166. isValidTime = false;
  167. second = 59;
  168. vals [2] = "59";
  169. }
  170. string t = isShort ? $" {hour,2:00}{sepChar}{minute,2:00}" : $" {hour,2:00}{sepChar}{minute,2:00}{sepChar}{second,2:00}";
  171. if (!DateTime.TryParseExact (t, Format, CultureInfo.CurrentCulture, DateTimeStyles.None, out DateTime result) ||
  172. !isValidTime)
  173. return false;
  174. Time = result;
  175. return true;
  176. }
  177. void IncCursorPosition ()
  178. {
  179. if (CursorPosition == FieldLen)
  180. return;
  181. if (Text [++CursorPosition] == sepChar.ToCharArray () [0])
  182. CursorPosition++;
  183. }
  184. void DecCursorPosition ()
  185. {
  186. if (CursorPosition == 1)
  187. return;
  188. if (Text [--CursorPosition] == sepChar.ToCharArray () [0])
  189. CursorPosition--;
  190. }
  191. void AdjCursorPosition ()
  192. {
  193. if (Text [CursorPosition] == sepChar.ToCharArray () [0])
  194. CursorPosition++;
  195. }
  196. ///<inheritdoc/>
  197. public override bool ProcessKey (KeyEvent kb)
  198. {
  199. switch (kb.Key) {
  200. case Key.DeleteChar:
  201. case Key.ControlD:
  202. if (ReadOnly)
  203. return true;
  204. SetText ('0');
  205. break;
  206. case Key.Delete:
  207. case Key.Backspace:
  208. if (ReadOnly)
  209. return true;
  210. SetText ('0');
  211. DecCursorPosition ();
  212. break;
  213. // Home, C-A
  214. case Key.Home:
  215. case Key.ControlA:
  216. CursorPosition = 1;
  217. break;
  218. case Key.CursorLeft:
  219. case Key.ControlB:
  220. DecCursorPosition ();
  221. break;
  222. case Key.End:
  223. case Key.ControlE: // End
  224. CursorPosition = FieldLen;
  225. break;
  226. case Key.CursorRight:
  227. case Key.ControlF:
  228. IncCursorPosition ();
  229. break;
  230. default:
  231. // Ignore non-numeric characters.
  232. if (kb.Key < (Key)((int)'0') || kb.Key > (Key)((int)'9'))
  233. return false;
  234. if (ReadOnly)
  235. return true;
  236. if (SetText (TextModel.ToRunes (ustring.Make ((uint)kb.Key)).First ()))
  237. IncCursorPosition ();
  238. return true;
  239. }
  240. return true;
  241. }
  242. ///<inheritdoc/>
  243. public override bool MouseEvent (MouseEvent ev)
  244. {
  245. if (!ev.Flags.HasFlag (MouseFlags.Button1Clicked))
  246. return false;
  247. if (!HasFocus)
  248. SuperView.SetFocus (this);
  249. var point = ev.X;
  250. if (point > FieldLen)
  251. point = FieldLen;
  252. if (point < 1)
  253. point = 1;
  254. CursorPosition = point;
  255. AdjCursorPosition ();
  256. return true;
  257. }
  258. /// <summary>
  259. /// Virtual method that will invoke the <see cref="TimeChanged"/> with a <see cref="DateTimeEventArgs"/>.
  260. /// </summary>
  261. /// <param name="args">The arguments of the <see cref="DateTimeEventArgs"/></param>
  262. public virtual void OnTimeChanged (DateTimeEventArgs args)
  263. {
  264. TimeChanged?.Invoke (this, args);
  265. }
  266. }
  267. }