DateField.cs 8.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332
  1. //
  2. // DateField.cs: text entry for date
  3. //
  4. // Author: Barry Nolte
  5. //
  6. // Licensed under the MIT license
  7. //
  8. using System;
  9. using System.Globalization;
  10. using System.Linq;
  11. using NStack;
  12. namespace Terminal.Gui {
  13. /// <summary>
  14. /// Date editing <see cref="View"/>
  15. /// </summary>
  16. /// <remarks>
  17. /// The <see cref="DateField"/> <see cref="View"/> provides date editing functionality with mouse support.
  18. /// </remarks>
  19. public class DateField : TextField {
  20. bool isShort;
  21. int longFieldLen = 10;
  22. int shortFieldLen = 8;
  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. /// Initializes a new instance of <see cref="DateField"/> using <see cref="LayoutStyle.Absolute"/> layout.
  30. /// </summary>
  31. /// <param name="x">The x coordinate.</param>
  32. /// <param name="y">The y coordinate.</param>
  33. /// <param name="date">Initial date contents.</param>
  34. /// <param name="isShort">If true, shows only two digits for the year.</param>
  35. public DateField (int x, int y, DateTime date, bool isShort = false) : base (x, y, isShort ? 10 : 12, "")
  36. {
  37. this.isShort = isShort;
  38. Initialize (date);
  39. }
  40. /// <summary>
  41. /// Initializes a new instance of <see cref="DateField"/> using <see cref="LayoutStyle.Computed"/> layout.
  42. /// </summary>
  43. public DateField () : this (DateTime.MinValue) { }
  44. /// <summary>
  45. /// Initializes a new instance of <see cref="DateField"/> using <see cref="LayoutStyle.Computed"/> layout.
  46. /// </summary>
  47. /// <param name="date"></param>
  48. public DateField (DateTime date) : base ("")
  49. {
  50. this.isShort = true;
  51. Width = FieldLen + 2;
  52. Initialize (date);
  53. }
  54. void Initialize (DateTime date)
  55. {
  56. CultureInfo cultureInfo = CultureInfo.CurrentCulture;
  57. sepChar = cultureInfo.DateTimeFormat.DateSeparator;
  58. longFormat = GetLongFormat (cultureInfo.DateTimeFormat.ShortDatePattern);
  59. shortFormat = GetShortFormat (longFormat);
  60. CursorPosition = 1;
  61. Date = date;
  62. Changed += DateField_Changed;
  63. }
  64. void DateField_Changed (object sender, ustring e)
  65. {
  66. if (!DateTime.TryParseExact (GetDate (Text).ToString (), GetInvarianteFormat (), CultureInfo.CurrentCulture, DateTimeStyles.None, out DateTime result))
  67. Text = e;
  68. }
  69. string GetInvarianteFormat ()
  70. {
  71. return $"MM{sepChar}dd{sepChar}yyyy";
  72. }
  73. string GetLongFormat (string lf)
  74. {
  75. ustring [] frm = ustring.Make (lf).Split (ustring.Make (sepChar));
  76. for (int i = 0; i < frm.Length; i++) {
  77. if (frm [i].Contains ("M") && frm [i].Length < 2)
  78. lf = lf.Replace ("M", "MM");
  79. if (frm [i].Contains ("d") && frm [i].Length < 2)
  80. lf = lf.Replace ("d", "dd");
  81. if (frm [i].Contains ("y") && frm [i].Length < 4)
  82. lf = lf.Replace ("yy", "yyyy");
  83. }
  84. return $" {lf}";
  85. }
  86. string GetShortFormat (string lf)
  87. {
  88. return lf.Replace ("yyyy", "yy");
  89. }
  90. /// <summary>
  91. /// Gets or sets the date of the <see cref="DateField"/>.
  92. /// </summary>
  93. /// <remarks>
  94. /// </remarks>
  95. public DateTime Date {
  96. get {
  97. if (!DateTime.TryParseExact (Text.ToString (), Format, CultureInfo.CurrentCulture, DateTimeStyles.None, out DateTime result)) return new DateTime ();
  98. return result;
  99. }
  100. set {
  101. this.Text = value.ToString (Format);
  102. }
  103. }
  104. /// <summary>
  105. /// Get or set the data format for the widget.
  106. /// </summary>
  107. public bool IsShortFormat {
  108. get => isShort;
  109. set {
  110. isShort = value;
  111. if (isShort)
  112. Width = 10;
  113. else
  114. Width = 12;
  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. ustring [] vals = text.Split (ustring.Make (sepChar));
  135. ustring [] frm = ustring.Make (Format).Split (ustring.Make (sepChar));
  136. bool isValidDate = true;
  137. int idx = GetFormatIndex (frm, "y");
  138. int year = Int32.Parse (vals [idx].ToString ());
  139. int month;
  140. int day;
  141. idx = GetFormatIndex (frm, "M");
  142. if (Int32.Parse (vals [idx].ToString ()) < 1) {
  143. isValidDate = false;
  144. month = 1;
  145. vals [idx] = "1";
  146. } else if (Int32.Parse (vals [idx].ToString ()) > 12) {
  147. isValidDate = false;
  148. month = 12;
  149. vals [idx] = "12";
  150. } else
  151. month = Int32.Parse (vals [idx].ToString ());
  152. idx = GetFormatIndex (frm, "d");
  153. if (Int32.Parse (vals [idx].ToString ()) < 1) {
  154. isValidDate = false;
  155. day = 1;
  156. vals [idx] = "1";
  157. } else if (Int32.Parse (vals [idx].ToString ()) > 31) {
  158. isValidDate = false;
  159. day = DateTime.DaysInMonth (year, month);
  160. vals [idx] = day.ToString ();
  161. } else
  162. day = Int32.Parse (vals [idx].ToString ());
  163. string date = GetDate (month, day, year, frm);
  164. Text = date;
  165. if (!DateTime.TryParseExact (date, Format, CultureInfo.CurrentCulture, DateTimeStyles.None, out DateTime result) ||
  166. !isValidDate)
  167. return false;
  168. return true;
  169. }
  170. string GetDate (int month, int day, int year, ustring [] fm)
  171. {
  172. string date = " ";
  173. for (int i = 0; i < fm.Length; i++) {
  174. if (fm [i].Contains ("M")) {
  175. date += $"{month,2:00}";
  176. } else if (fm [i].Contains ("d")) {
  177. date += $"{day,2:00}";
  178. } else {
  179. if (!isShort && year.ToString ().Length == 2) {
  180. var y = DateTime.Now.Year.ToString ();
  181. date += y.Substring (0, 2) + year.ToString ();
  182. } else {
  183. date += $"{year,2:00}";
  184. }
  185. }
  186. if (i < 2)
  187. date += $"{sepChar}";
  188. }
  189. return date;
  190. }
  191. ustring GetDate (ustring text)
  192. {
  193. ustring [] vals = text.Split (ustring.Make (sepChar));
  194. ustring [] frm = ustring.Make (Format).Split (ustring.Make (sepChar));
  195. ustring [] date = { null, null, null };
  196. for (int i = 0; i < frm.Length; i++) {
  197. if (frm [i].Contains ("M")) {
  198. date [0] = vals [i].TrimSpace ();
  199. } else if (frm [i].Contains ("d")) {
  200. date [1] = vals [i].TrimSpace ();
  201. } else {
  202. var year = vals [i].TrimSpace ();
  203. if (year.Length == 2) {
  204. var y = DateTime.Now.Year.ToString ();
  205. date [2] = y.Substring (0, 2) + year.ToString ();
  206. } else {
  207. date [2] = vals [i].TrimSpace ();
  208. }
  209. }
  210. }
  211. return date [0] + ustring.Make (sepChar) + date [1] + ustring.Make (sepChar) + date [2];
  212. }
  213. int GetFormatIndex (ustring [] fm, string t)
  214. {
  215. int idx = -1;
  216. for (int i = 0; i < fm.Length; i++) {
  217. if (fm [i].Contains (t)) {
  218. idx = i;
  219. break;
  220. }
  221. }
  222. return idx;
  223. }
  224. void IncCursorPosition ()
  225. {
  226. if (CursorPosition == FieldLen)
  227. return;
  228. if (Text [++CursorPosition] == sepChar.ToCharArray () [0])
  229. CursorPosition++;
  230. }
  231. void DecCursorPosition ()
  232. {
  233. if (CursorPosition == 1)
  234. return;
  235. if (Text [--CursorPosition] == sepChar.ToCharArray () [0])
  236. CursorPosition--;
  237. }
  238. void AdjCursorPosition ()
  239. {
  240. if (Text [CursorPosition] == sepChar.ToCharArray () [0])
  241. CursorPosition++;
  242. }
  243. /// <inheritdoc/>
  244. public override bool ProcessKey (KeyEvent kb)
  245. {
  246. switch (kb.Key) {
  247. case Key.DeleteChar:
  248. case Key.ControlD:
  249. SetText ('0');
  250. break;
  251. case Key.Delete:
  252. case Key.Backspace:
  253. SetText ('0');
  254. DecCursorPosition ();
  255. break;
  256. // Home, C-A
  257. case Key.Home:
  258. case Key.ControlA:
  259. CursorPosition = 1;
  260. break;
  261. case Key.CursorLeft:
  262. case Key.ControlB:
  263. DecCursorPosition ();
  264. break;
  265. case Key.End:
  266. case Key.ControlE: // End
  267. CursorPosition = FieldLen;
  268. break;
  269. case Key.CursorRight:
  270. case Key.ControlF:
  271. IncCursorPosition ();
  272. break;
  273. default:
  274. // Ignore non-numeric characters.
  275. if (kb.Key < (Key)((int)'0') || kb.Key > (Key)((int)'9'))
  276. return false;
  277. if (SetText (TextModel.ToRunes (ustring.Make ((uint)kb.Key)).First ()))
  278. IncCursorPosition ();
  279. return true;
  280. }
  281. return true;
  282. }
  283. /// <inheritdoc/>
  284. public override bool MouseEvent (MouseEvent ev)
  285. {
  286. if (!ev.Flags.HasFlag (MouseFlags.Button1Clicked))
  287. return false;
  288. if (!HasFocus)
  289. SuperView.SetFocus (this);
  290. var point = ev.X;
  291. if (point > FieldLen)
  292. point = FieldLen;
  293. if (point < 1)
  294. point = 1;
  295. CursorPosition = point;
  296. AdjCursorPosition ();
  297. return true;
  298. }
  299. }
  300. }