diff --git a/Terminal.Gui/Resources/Strings.Designer.cs b/Terminal.Gui/Resources/Strings.Designer.cs index 3a77922a0..408e14395 100644 --- a/Terminal.Gui/Resources/Strings.Designer.cs +++ b/Terminal.Gui/Resources/Strings.Designer.cs @@ -186,6 +186,15 @@ namespace Terminal.Gui.Resources { } } + /// + /// Looks up a localized string similar to Date Picker. + /// + internal static string dpTitle { + get { + return ResourceManager.GetString("dpTitle", resourceCulture); + } + } + /// /// Looks up a localized string similar to Any Files. /// diff --git a/Terminal.Gui/Resources/Strings.fr-FR.resx b/Terminal.Gui/Resources/Strings.fr-FR.resx index e6dcfcaea..746c45499 100644 --- a/Terminal.Gui/Resources/Strings.fr-FR.resx +++ b/Terminal.Gui/Resources/Strings.fr-FR.resx @@ -177,4 +177,7 @@ Ouvrir + + Sélecteur de Date + \ No newline at end of file diff --git a/Terminal.Gui/Resources/Strings.ja-JP.resx b/Terminal.Gui/Resources/Strings.ja-JP.resx index 14e5ac1de..4a825c51c 100644 --- a/Terminal.Gui/Resources/Strings.ja-JP.resx +++ b/Terminal.Gui/Resources/Strings.ja-JP.resx @@ -273,4 +273,7 @@ {0}で降順ソート (_S) + + 日付ピッカー + \ No newline at end of file diff --git a/Terminal.Gui/Resources/Strings.pt-PT.resx b/Terminal.Gui/Resources/Strings.pt-PT.resx index bfd60ce15..95cdc8569 100644 --- a/Terminal.Gui/Resources/Strings.pt-PT.resx +++ b/Terminal.Gui/Resources/Strings.pt-PT.resx @@ -177,4 +177,7 @@ Abrir + + Seletor de Data + \ No newline at end of file diff --git a/Terminal.Gui/Resources/Strings.resx b/Terminal.Gui/Resources/Strings.resx index d8159c6e0..1152cfb1d 100644 --- a/Terminal.Gui/Resources/Strings.resx +++ b/Terminal.Gui/Resources/Strings.resx @@ -277,4 +277,7 @@ _Sort {0} DESC + + Date Picker + \ No newline at end of file diff --git a/Terminal.Gui/Resources/Strings.zh-Hans.resx b/Terminal.Gui/Resources/Strings.zh-Hans.resx index da767eaa3..009fdd479 100644 --- a/Terminal.Gui/Resources/Strings.zh-Hans.resx +++ b/Terminal.Gui/Resources/Strings.zh-Hans.resx @@ -273,4 +273,7 @@ {0}逆序排序 (_S) + + 日期选择器 + \ No newline at end of file diff --git a/Terminal.Gui/Views/DatePicker.cs b/Terminal.Gui/Views/DatePicker.cs new file mode 100644 index 000000000..109752a7a --- /dev/null +++ b/Terminal.Gui/Views/DatePicker.cs @@ -0,0 +1,241 @@ +// +// DatePicker.cs: DatePicker control +// +// Author: Maciej Winnik +// +using System; +using System.Data; +using System.Globalization; +using System.Linq; + +namespace Terminal.Gui.Views; + +/// +/// The Date Picker. +/// +public class DatePicker : View { + + private DateField _dateField; + private Label _dateLabel; + private TableView _calendar; + private DataTable _table; + private Button _nextMonthButton; + private Button _previousMonthButton; + + private DateTime _date = DateTime.Now; + + /// + /// Format of date. The default is MM/dd/yyyy. + /// + public string Format { get; set; } = CultureInfo.CurrentCulture.DateTimeFormat.ShortDatePattern; + + /// + /// Get or set the date. + /// + public DateTime Date { + get => _date; + set { + _date = value; + Text = _date.ToString (Format); + } + } + + /// + /// Initializes a new instance of . + /// + public DatePicker () => SetInitialProperties (_date); + + /// + /// Initializes a new instance of with the specified date. + /// + public DatePicker (DateTime date) + { + SetInitialProperties (date); + } + + /// + /// Initializes a new instance of with the specified date and format. + /// + public DatePicker (DateTime date, string format) + { + Format = format; + SetInitialProperties (date); + } + + private void SetInitialProperties (DateTime date) + { + Title = "Date Picker"; + BorderStyle = LineStyle.Single; + Date = date; + _dateLabel = new Label ("Date: ") { + X = 0, + Y = 0, + Height = 1, + }; + + _dateField = new DateField (DateTime.Now) { + X = Pos.Right (_dateLabel), + Y = 0, + Width = Dim.Fill (1), + Height = 1, + IsShortFormat = false + }; + + _calendar = new TableView () { + X = 0, + Y = Pos.Bottom (_dateLabel), + Height = 11, + Style = new TableStyle { + ShowHeaders = true, + ShowHorizontalBottomline = true, + ShowVerticalCellLines = true, + ExpandLastColumn = true, + } + }; + + _previousMonthButton = new Button (GetBackButtonText ()) { + X = Pos.Center () - 4, + Y = Pos.Bottom (_calendar) - 1, + Height = 1, + Width = CalculateCalendarWidth () / 2 + }; + + _previousMonthButton.Clicked += (sender, e) => { + Date = _date.AddMonths (-1); + CreateCalendar (); + _dateField.Date = Date; + }; + + _nextMonthButton = new Button (GetForwardButtonText ()) { + X = Pos.Right (_previousMonthButton) + 2, + Y = Pos.Bottom (_calendar) - 1, + Height = 1, + Width = CalculateCalendarWidth () / 2 + }; + + _nextMonthButton.Clicked += (sender, e) => { + Date = _date.AddMonths (1); + CreateCalendar (); + _dateField.Date = Date; + }; + + CreateCalendar (); + SelectDayOnCalendar (_date.Day); + + _calendar.CellActivated += (sender, e) => { + var dayValue = _table.Rows [e.Row] [e.Col]; + if (dayValue is null) { + return; + } + bool isDay = int.TryParse (dayValue.ToString (), out int day); + if (!isDay) { + return; + } + ChangeDayDate (day); + SelectDayOnCalendar (day); + Text = _date.ToString (Format); + + }; + + Width = CalculateCalendarWidth () + 2; + Height = _calendar.Height + 3; + + _dateField.DateChanged += DateField_DateChanged; + + Add (_dateLabel, _dateField, _calendar, _previousMonthButton, _nextMonthButton); + } + + private void DateField_DateChanged (object sender, DateTimeEventArgs e) + { + if (e.NewValue.Date.Day != _date.Day) { + SelectDayOnCalendar (e.NewValue.Day); + } + Date = e.NewValue; + CreateCalendar (); + SelectDayOnCalendar (_date.Day); + } + + private void CreateCalendar () + { + _calendar.Table = new DataTableSource (_table = CreateDataTable (_date.Month, _date.Year)); + } + + private void ChangeDayDate (int day) + { + _date = new DateTime (_date.Year, _date.Month, day); + _dateField.Date = _date; + CreateCalendar (); + } + + private DataTable CreateDataTable (int month, int year) + { + _table = new DataTable (); + GenerateCalendarLabels (); + int amountOfDaysInMonth = DateTime.DaysInMonth (year, month); + DateTime dateValue = new DateTime (year, month, 1); + var dayOfWeek = dateValue.DayOfWeek; + + _table.Rows.Add (new object [6]); + for (int i = 1; i <= amountOfDaysInMonth; i++) { + _table.Rows [^1] [(int)dayOfWeek] = i; + if (dayOfWeek == DayOfWeek.Saturday && i != amountOfDaysInMonth) { + _table.Rows.Add (new object [7]); + } + dayOfWeek = dayOfWeek == DayOfWeek.Saturday ? DayOfWeek.Sunday : dayOfWeek + 1; + } + int missingRows = 6 - _table.Rows.Count; + for (int i = 0; i < missingRows; i++) { + _table.Rows.Add (new object [7]); + } + + return _table; + } + + private void GenerateCalendarLabels () + { + _calendar.Style.ColumnStyles.Clear (); + for (int i = 0; i < 7; i++) { + var abbreviatedDayName = CultureInfo.CurrentCulture.DateTimeFormat.GetAbbreviatedDayName ((DayOfWeek)i); + _calendar.Style.ColumnStyles.Add (i, new ColumnStyle () { + MaxWidth = abbreviatedDayName.Length, + MinWidth = abbreviatedDayName.Length, + MinAcceptableWidth = abbreviatedDayName.Length + }); + _table.Columns.Add (abbreviatedDayName); + } + _calendar.Width = CalculateCalendarWidth (); + } + + private int CalculateCalendarWidth () + { + return _calendar.Style.ColumnStyles.Sum (c => c.Value.MinWidth) + 7; + } + + private void SelectDayOnCalendar (int day) + { + for (int i = 0; i < _table.Rows.Count; i++) { + for (int j = 0; j < _table.Columns.Count; j++) { + if (_table.Rows [i] [j].ToString () == day.ToString ()) { + _calendar.SetSelection (j, i, false); + return; + } + } + } + } + + private string GetForwardButtonText () => Glyphs.RightArrow.ToString () + Glyphs.RightArrow.ToString (); + + private string GetBackButtonText () => Glyphs.LeftArrow.ToString () + Glyphs.LeftArrow.ToString (); + + /// + protected override void Dispose (bool disposing) + { + _dateLabel.Dispose (); + _calendar.Dispose (); + _dateField.Dispose (); + _table.Dispose (); + _previousMonthButton.Dispose (); + _nextMonthButton.Dispose (); + base.Dispose (disposing); + } +} diff --git a/UICatalog/Scenarios/DatePickers.cs b/UICatalog/Scenarios/DatePickers.cs new file mode 100644 index 000000000..5de075ef0 --- /dev/null +++ b/UICatalog/Scenarios/DatePickers.cs @@ -0,0 +1,22 @@ +using Terminal.Gui; +using Terminal.Gui.Views; + +namespace UICatalog.Scenarios; +[ScenarioMetadata (Name: "Date Picker", Description: "Demonstrates how to use DatePicker class")] +[ScenarioCategory ("Controls")] +[ScenarioCategory ("DateTime")] +public class DatePickers : Scenario { + + + public override void Setup () + { + var datePicker = new DatePicker () { + Y = Pos.Center (), + X = Pos.Center () + }; + + + Win.Add (datePicker); + } +} + diff --git a/UnitTests/Views/AllViewsTests.cs b/UnitTests/Views/AllViewsTests.cs index c743d276a..2b1229e6a 100644 --- a/UnitTests/Views/AllViewsTests.cs +++ b/UnitTests/Views/AllViewsTests.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.IO; using System.Linq; using System.Reflection; +using Terminal.Gui.Views; using Xunit; using Xunit.Abstractions; @@ -115,6 +116,10 @@ public class AllViewsTests { if (vType is TextView) { top.NewKeyDownEvent (new (KeyCode.Tab | KeyCode.CtrlMask)); + } else if (vType is DatePicker) { + for (int i = 0; i < 4; i++) { + top.NewKeyDownEvent (new (KeyCode.Tab | KeyCode.CtrlMask)); + } } else { top.NewKeyDownEvent (new (KeyCode.Tab)); } diff --git a/UnitTests/Views/DatePickerTests.cs b/UnitTests/Views/DatePickerTests.cs new file mode 100644 index 000000000..8685af417 --- /dev/null +++ b/UnitTests/Views/DatePickerTests.cs @@ -0,0 +1,54 @@ +using System; +using System.Globalization; +using Terminal.Gui.Views; +using Xunit; + +namespace Terminal.Gui.ViewsTests; + +public class DatePickerTests { + + [Fact] + public void DatePicker_SetFormat_ShouldChangeFormat () + { + var datePicker = new DatePicker { + Format = "dd/MM/yyyy" + }; + Assert.Equal ("dd/MM/yyyy", datePicker.Format); + } + + [Fact] + public void DatePicker_Initialize_ShouldSetCurrentDate () + { + var datePicker = new DatePicker (); + var format = CultureInfo.CurrentCulture.DateTimeFormat.ShortDatePattern; + Assert.Equal (DateTime.Now.ToString (format), datePicker.Text); + } + + [Fact] + public void DatePicker_SetDate_ShouldChangeText () + { + var datePicker = new DatePicker (); + var newDate = new DateTime (2024, 1, 15); + var format = CultureInfo.CurrentCulture.DateTimeFormat.ShortDatePattern; + + datePicker.Date = newDate; + Assert.Equal (newDate.ToString (format), datePicker.Text); + } + + [Fact] + public void DatePicker_ShowDatePickerDialog_ShouldChangeDate () + { + var datePicker = new DatePicker (); + var format = CultureInfo.CurrentCulture.DateTimeFormat.ShortDatePattern; + var originalDate = datePicker.Date; + + datePicker.MouseEvent (new MouseEvent () { Flags = MouseFlags.Button1Clicked, X = 4, Y = 1 }); + + var newDate = new DateTime (2024, 2, 20); + datePicker.Date = newDate; + + Assert.Equal (newDate.ToString (format), datePicker.Text); + + datePicker.Date = originalDate; + } +}