DateField.cs 7.2 KB

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