DateField.cs 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327
  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"/> at an absolute position and fixed size.
  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"/>
  42. /// </summary>
  43. /// <param name="date"></param>
  44. public DateField (DateTime date) : base ("")
  45. {
  46. this.isShort = true;
  47. Width = FieldLen + 2;
  48. Initialize (date);
  49. }
  50. void Initialize (DateTime date)
  51. {
  52. CultureInfo cultureInfo = CultureInfo.CurrentCulture;
  53. sepChar = cultureInfo.DateTimeFormat.DateSeparator;
  54. longFormat = GetLongFormat (cultureInfo.DateTimeFormat.ShortDatePattern);
  55. shortFormat = GetShortFormat (longFormat);
  56. CursorPosition = 1;
  57. Date = date;
  58. Changed += DateField_Changed;
  59. }
  60. void DateField_Changed (object sender, ustring e)
  61. {
  62. if (!DateTime.TryParseExact (GetDate (Text).ToString(), GetInvarianteFormat (), CultureInfo.CurrentCulture, DateTimeStyles.None, out DateTime result))
  63. Text = e;
  64. }
  65. string GetInvarianteFormat ()
  66. {
  67. return $"MM{sepChar}dd{sepChar}yyyy";
  68. }
  69. string GetLongFormat (string lf)
  70. {
  71. ustring [] frm = ustring.Make (lf).Split (ustring.Make (sepChar));
  72. for (int i = 0; i < frm.Length; i++) {
  73. if (frm [i].Contains ("M") && frm [i].Length < 2)
  74. lf = lf.Replace ("M", "MM");
  75. if (frm [i].Contains ("d") && frm [i].Length < 2)
  76. lf = lf.Replace ("d", "dd");
  77. if (frm [i].Contains ("y") && frm [i].Length < 4)
  78. lf = lf.Replace ("yy", "yyyy");
  79. }
  80. return $" {lf}";
  81. }
  82. string GetShortFormat (string lf)
  83. {
  84. return lf.Replace ("yyyy", "yy");
  85. }
  86. /// <summary>
  87. /// Gets or sets the date of the <see cref="DateField"/>.
  88. /// </summary>
  89. /// <remarks>
  90. /// </remarks>
  91. public DateTime Date {
  92. get {
  93. if (!DateTime.TryParseExact (Text.ToString (), Format, CultureInfo.CurrentCulture, DateTimeStyles.None, out DateTime result)) return new DateTime ();
  94. return result;
  95. }
  96. set {
  97. this.Text = value.ToString (Format);
  98. }
  99. }
  100. /// <summary>
  101. /// Get or set the data format for the widget.
  102. /// </summary>
  103. public bool IsShortFormat {
  104. get => isShort;
  105. set {
  106. isShort = value;
  107. if (isShort)
  108. Width = 10;
  109. else
  110. Width = 12;
  111. var ro = ReadOnly;
  112. if (ro)
  113. ReadOnly = false;
  114. SetText (Text);
  115. ReadOnly = ro;
  116. SetNeedsDisplay ();
  117. }
  118. }
  119. bool SetText (Rune key)
  120. {
  121. var text = TextModel.ToRunes (Text);
  122. var newText = text.GetRange (0, CursorPosition);
  123. newText.Add (key);
  124. if (CursorPosition < FieldLen)
  125. newText = newText.Concat (text.GetRange (CursorPosition + 1, text.Count - (CursorPosition + 1))).ToList ();
  126. return SetText (ustring.Make (newText));
  127. }
  128. bool SetText (ustring text)
  129. {
  130. ustring [] vals = text.Split (ustring.Make (sepChar));
  131. ustring [] frm = ustring.Make (Format).Split (ustring.Make (sepChar));
  132. bool isValidDate = true;
  133. int idx = GetFormatIndex (frm, "y");
  134. int year = Int32.Parse (vals [idx].ToString ());
  135. int month;
  136. int day;
  137. idx = GetFormatIndex (frm, "M");
  138. if (Int32.Parse (vals [idx].ToString ()) < 1) {
  139. isValidDate = false;
  140. month = 1;
  141. vals [idx] = "1";
  142. } else if (Int32.Parse (vals [idx].ToString ()) > 12) {
  143. isValidDate = false;
  144. month = 12;
  145. vals [idx] = "12";
  146. } else
  147. month = Int32.Parse (vals [idx].ToString ());
  148. idx = GetFormatIndex (frm, "d");
  149. if (Int32.Parse (vals [idx].ToString ()) < 1) {
  150. isValidDate = false;
  151. day = 1;
  152. vals [idx] = "1";
  153. } else if (Int32.Parse (vals [idx].ToString ()) > 31) {
  154. isValidDate = false;
  155. day = DateTime.DaysInMonth (year, month);
  156. vals [idx] = day.ToString ();
  157. } else
  158. day = Int32.Parse (vals [idx].ToString ());
  159. string date = GetDate (month, day, year, frm);
  160. Text = date;
  161. if (!DateTime.TryParseExact (date, Format, CultureInfo.CurrentCulture, DateTimeStyles.None, out DateTime result) ||
  162. !isValidDate)
  163. return false;
  164. return true;
  165. }
  166. string GetDate (int month, int day, int year, ustring [] fm)
  167. {
  168. string date = " ";
  169. for (int i = 0; i < fm.Length; i++) {
  170. if (fm [i].Contains ("M")) {
  171. date += $"{month,2:00}";
  172. } else if (fm [i].Contains ("d")) {
  173. date += $"{day,2:00}";
  174. } else {
  175. if (!isShort && year.ToString ().Length == 2) {
  176. var y = DateTime.Now.Year.ToString ();
  177. date += y.Substring (0, 2) + year.ToString ();
  178. } else {
  179. date += $"{year,2:00}";
  180. }
  181. }
  182. if (i < 2)
  183. date += $"{sepChar}";
  184. }
  185. return date;
  186. }
  187. ustring GetDate (ustring text)
  188. {
  189. ustring [] vals = text.Split (ustring.Make (sepChar));
  190. ustring [] frm = ustring.Make (Format).Split (ustring.Make (sepChar));
  191. ustring [] date = { null, null, null };
  192. for (int i = 0; i < frm.Length; i++) {
  193. if (frm [i].Contains ("M")) {
  194. date [0] = vals [i].TrimSpace ();
  195. } else if (frm [i].Contains ("d")) {
  196. date [1] = vals [i].TrimSpace ();
  197. } else {
  198. var year = vals [i].TrimSpace ();
  199. if (year.Length == 2) {
  200. var y = DateTime.Now.Year.ToString ();
  201. date [2] = y.Substring (0, 2) + year.ToString ();
  202. } else {
  203. date [2] = vals [i].TrimSpace ();
  204. }
  205. }
  206. }
  207. return date [0] + ustring.Make (sepChar) + date [1] + ustring.Make (sepChar) + date [2];
  208. }
  209. int GetFormatIndex (ustring [] fm, string t)
  210. {
  211. int idx = -1;
  212. for (int i = 0; i < fm.Length; i++) {
  213. if (fm [i].Contains (t)) {
  214. idx = i;
  215. break;
  216. }
  217. }
  218. return idx;
  219. }
  220. void IncCursorPosition ()
  221. {
  222. if (CursorPosition == FieldLen)
  223. return;
  224. if (Text [++CursorPosition] == sepChar.ToCharArray () [0])
  225. CursorPosition++;
  226. }
  227. void DecCursorPosition ()
  228. {
  229. if (CursorPosition == 1)
  230. return;
  231. if (Text [--CursorPosition] == sepChar.ToCharArray () [0])
  232. CursorPosition--;
  233. }
  234. void AdjCursorPosition ()
  235. {
  236. if (Text [CursorPosition] == sepChar.ToCharArray () [0])
  237. CursorPosition++;
  238. }
  239. ///<inheritdoc cref="ProcessKey(KeyEvent)"/>
  240. public override bool ProcessKey(KeyEvent kb)
  241. {
  242. switch (kb.Key) {
  243. case Key.DeleteChar:
  244. case Key.ControlD:
  245. SetText ('0');
  246. break;
  247. case Key.Delete:
  248. case Key.Backspace:
  249. SetText ('0');
  250. DecCursorPosition ();
  251. break;
  252. // Home, C-A
  253. case Key.Home:
  254. case Key.ControlA:
  255. CursorPosition = 1;
  256. break;
  257. case Key.CursorLeft:
  258. case Key.ControlB:
  259. DecCursorPosition ();
  260. break;
  261. case Key.End:
  262. case Key.ControlE: // End
  263. CursorPosition = FieldLen;
  264. break;
  265. case Key.CursorRight:
  266. case Key.ControlF:
  267. IncCursorPosition ();
  268. break;
  269. default:
  270. // Ignore non-numeric characters.
  271. if (kb.Key < (Key)((int)'0') || kb.Key > (Key)((int)'9'))
  272. return false;
  273. if (SetText (TextModel.ToRunes (ustring.Make ((uint)kb.Key)).First ()))
  274. IncCursorPosition ();
  275. return true;
  276. }
  277. return true;
  278. }
  279. ///<inheritdoc cref="MouseEvent(Gui.MouseEvent)"/>
  280. public override bool MouseEvent(MouseEvent ev)
  281. {
  282. if (!ev.Flags.HasFlag (MouseFlags.Button1Clicked))
  283. return false;
  284. if (!HasFocus)
  285. SuperView.SetFocus (this);
  286. var point = ev.X;
  287. if (point > FieldLen)
  288. point = FieldLen;
  289. if (point < 1)
  290. point = 1;
  291. CursorPosition = point;
  292. AdjCursorPosition ();
  293. return true;
  294. }
  295. }
  296. }