From ae07fae1c48993cb88980aed4a0e88f571ef0c0d Mon Sep 17 00:00:00 2001 From: BDisp Date: Sat, 16 Nov 2019 15:24:53 +0000 Subject: [PATCH] Fixes culture info of DataField from pr #250 --- .gitignore | 3 +- Example/demo.cs | 4 +- Terminal.Gui/Views/DateField.cs | 243 ++++++++++++++++++++++++++++++++ Terminal.Gui/Views/TextField.cs | 7 +- 4 files changed, 252 insertions(+), 5 deletions(-) create mode 100644 Terminal.Gui/Views/DateField.cs diff --git a/.gitignore b/.gitignore index f10938059..c4865c0ac 100644 --- a/.gitignore +++ b/.gitignore @@ -4,4 +4,5 @@ obj *.userprefs *~ packages -.vs \ No newline at end of file +.vs +*.csproj.user diff --git a/Example/demo.cs b/Example/demo.cs index 34a78ec2e..c6fc780f9 100644 --- a/Example/demo.cs +++ b/Example/demo.cs @@ -142,8 +142,10 @@ static class Demo { new Button (10, 19, "Cancel"), new TimeField (3, 20, DateTime.Now), new TimeField (23, 20, DateTime.Now, true), + new DateField(3, 22, DateTime.Now), + new DateField(23, 22, DateTime.Now, true), progress, - new Label (3, 22, "Press F9 (on Unix, ESC+9 is an alias) to activate the menubar") + new Label (3, 24, "Press F9 (on Unix, ESC+9 is an alias) to activate the menubar") ); diff --git a/Terminal.Gui/Views/DateField.cs b/Terminal.Gui/Views/DateField.cs new file mode 100644 index 000000000..1a13c0fe1 --- /dev/null +++ b/Terminal.Gui/Views/DateField.cs @@ -0,0 +1,243 @@ +// +// DateField.cs: text entry for date +// +// Author: Barry Nolte +// +// Licensed under the MIT license +// +using System; +using System.Globalization; +using System.Linq; +using NStack; + +namespace Terminal.Gui { + + /// + /// Date edit widget + /// + /// + /// This widget provides date editing functionality, and mouse support. + /// + public class DateField : TextField { + bool isShort; + + int longFieldLen = 10; + int shortFieldLen = 8; + int FieldLen { get { return isShort ? shortFieldLen : longFieldLen; } } + char sepChar; // = '/'; + string longFormat; // = " MM/dd/yyyy"; + string shortFormat; // = " MM/dd/yy"; + string Format { get { return isShort ? shortFormat : longFormat; } } + + + /// + /// Public constructor that creates a date edit field at an absolute position and fixed size. + /// + /// The x coordinate. + /// The y coordinate. + /// Initial date contents. + /// If true, shows only two digits for the year. + public DateField(int x, int y, DateTime date, bool isShort = false) : base(x, y, isShort ? 10 : 12, "") + { + CultureInfo cultureInfo = CultureInfo.CurrentCulture; + sepChar = cultureInfo.DateTimeFormat.DateSeparator.ToCharArray()[0]; + longFormat = $" {cultureInfo.DateTimeFormat.ShortDatePattern}"; + shortFormat = GetShortFormat(longFormat); + this.isShort = isShort; + CursorPosition = 1; + Date = date; + Changed += DateField_Changed; + } + + private void DateField_Changed(object sender, ustring e) + { + if (!DateTime.TryParseExact(Text.ToString(), Format, CultureInfo.CurrentCulture, DateTimeStyles.None, out DateTime result)) + Text = e; + } + + string GetShortFormat(string lf) + { + return lf.Replace("yyyy", "yy"); + } + + /// + /// Gets or sets the date in the widget. + /// + /// + /// + public DateTime Date { + get { + if (!DateTime.TryParseExact(Text.ToString(), Format, CultureInfo.CurrentCulture, DateTimeStyles.None, out DateTime result)) return new DateTime(); + return result; + } + set { + this.Text = value.ToString(Format); + } + } + + bool SetText(Rune key) + { + var text = TextModel.ToRunes(Text); + var newText = text.GetRange(0, CursorPosition); + newText.Add(key); + if (CursorPosition < FieldLen) + newText = newText.Concat(text.GetRange(CursorPosition + 1, text.Count - (CursorPosition + 1))).ToList(); + return SetText(ustring.Make(newText)); + } + + bool SetText(ustring text) + { + // FIXED: This validation could be made better by calculating the actual min/max values + // for month/day/year. This has a 'good' chance of keeping things valid + ustring[] vals = text.Split(ustring.Make(sepChar)); + ustring[] frm = ustring.Make(Format).Split(ustring.Make(sepChar)); + bool isValidDate = true; + int idx = GetFormatIndex(frm, "y"); + int year = Int32.Parse(vals[idx].ToString()); + int month; + int day; + idx = GetFormatIndex(frm, "M"); + if (Int32.Parse(vals[idx].ToString()) < 1) { + isValidDate = false; + month = 1; + vals[idx] = "1"; + } else if (Int32.Parse(vals[idx].ToString()) > 12) { + isValidDate = false; + month = 12; + vals[idx] = "12"; + } else + month = Int32.Parse(vals[idx].ToString()); + idx = GetFormatIndex(frm, "d"); + if (Int32.Parse(vals[idx].ToString()) < 1) { + isValidDate = false; + day = 1; + vals[idx] = "1"; + } else if (Int32.Parse(vals[idx].ToString()) > 31) { + isValidDate = false; + day = DateTime.DaysInMonth(year, month); + vals[idx] = day.ToString(); + } else + day = Int32.Parse(vals[idx].ToString()); + string date = GetData(month, day, year, frm); + Text = date; + + if (!DateTime.TryParseExact(date, Format, CultureInfo.CurrentCulture, DateTimeStyles.None, out DateTime result) || + !isValidDate) + return false; + return true; + } + + string GetData(int month, int day, int year, ustring[] fm) + { + string data = " "; + for (int i = 0; i < fm.Length; i++) { + if (fm[i].Contains("M")) + data += $"{month,2:00}"; + else if (fm[i].Contains("d")) + data += $"{day,2:00}"; + else + data += isShort ? $"{year,2:00}" : $"{year,4:0000}"; + if (i < 2) + data += $"{sepChar}"; + } + return data; + } + + int GetFormatIndex(ustring[] fm, string t) + { + int idx = -1; + for (int i = 0; i < fm.Length; i++) { + if (fm[i].Contains(t)) { + idx = i; + break; + } + } + return idx; + } + + void IncCursorPosition() + { + if (CursorPosition == FieldLen) + return; + if (Text[++CursorPosition] == sepChar) + CursorPosition++; + } + + void DecCursorPosition() + { + if (CursorPosition == 1) + return; + if (Text[--CursorPosition] == sepChar) + CursorPosition--; + } + + void AdjCursorPosition() + { + if (Text[CursorPosition] == sepChar) + CursorPosition++; + } + + public override bool ProcessKey(KeyEvent kb) + { + switch (kb.Key) { + case Key.DeleteChar: + case Key.ControlD: + SetText('0'); + break; + + case Key.Delete: + case Key.Backspace: + SetText('0'); + DecCursorPosition(); + break; + + // Home, C-A + case Key.Home: + case Key.ControlA: + CursorPosition = 1; + break; + + case Key.CursorLeft: + case Key.ControlB: + DecCursorPosition(); + break; + + case Key.End: + case Key.ControlE: // End + CursorPosition = FieldLen; + break; + + case Key.CursorRight: + case Key.ControlF: + IncCursorPosition(); + break; + + default: + // Ignore non-numeric characters. + if (kb.Key < (Key)((int)'0') || kb.Key > (Key)((int)'9')) + return false; + if (SetText(TextModel.ToRunes(ustring.Make((uint)kb.Key)).First())) + IncCursorPosition(); + return true; + } + return true; + } + + public override bool MouseEvent(MouseEvent ev) + { + if (!ev.Flags.HasFlag(MouseFlags.Button1Clicked)) + return false; + if (!HasFocus) + SuperView.SetFocus(this); + + var point = ev.X; + if (point > FieldLen) + point = FieldLen; + if (point < 1) + point = 1; + CursorPosition = point; + AdjCursorPosition(); + return true; + } + } +} \ No newline at end of file diff --git a/Terminal.Gui/Views/TextField.cs b/Terminal.Gui/Views/TextField.cs index 215c66064..16187a339 100644 --- a/Terminal.Gui/Views/TextField.cs +++ b/Terminal.Gui/Views/TextField.cs @@ -35,7 +35,7 @@ namespace Terminal.Gui { /// Client code can hook up to this event, it is /// raised when the text in the entry changes. /// - public event EventHandler Changed; + public event EventHandler Changed; /// /// Public constructor that creates a text field, with layout controlled with X, Y, Width and Height. @@ -98,7 +98,10 @@ namespace Terminal.Gui { } set { + ustring oldText = ustring.Make(text); text = TextModel.ToRunes (value); + Changed?.Invoke(this, oldText); + if (point > text.Count) point = Math.Max (text.Count-1, 0); @@ -193,8 +196,6 @@ namespace Terminal.Gui { void SetText (List newText) { text = newText; - if (Changed != null) - Changed (this, EventArgs.Empty); } void SetText (IEnumerable newText)