DateField.cs 7.3 KB

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