//
// 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; } }
string sepChar;
string longFormat;
string shortFormat;
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;
longFormat = $" {cultureInfo.DateTimeFormat.ShortDatePattern}";
shortFormat = GetShortFormat(longFormat);
this.isShort = isShort;
CursorPosition = 1;
Date = date;
Changed += DateField_Changed;
}
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)
{
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.ToCharArray()[0])
CursorPosition++;
}
void DecCursorPosition()
{
if (CursorPosition == 1)
return;
if (Text[--CursorPosition] == sepChar.ToCharArray()[0])
CursorPosition--;
}
void AdjCursorPosition()
{
if (Text[CursorPosition] == sepChar.ToCharArray()[0])
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;
}
}
}