mirror of
https://github.com/gui-cs/Terminal.Gui.git
synced 2025-12-28 16:58:01 +01:00
299 lines
7.2 KiB
C#
299 lines
7.2 KiB
C#
//
|
|
// TimeField.cs: text entry for time
|
|
//
|
|
// Author: Jörg Preiß
|
|
//
|
|
// Licensed under the MIT license
|
|
using System;
|
|
using System.Globalization;
|
|
using System.Linq;
|
|
using NStack;
|
|
|
|
namespace Terminal.Gui {
|
|
/// <summary>
|
|
/// Time editing <see cref="View"/>
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// The <see cref="TimeField"/> <see cref="View"/> provides time editing functionality with mouse support.
|
|
/// </remarks>
|
|
public class TimeField : TextField {
|
|
TimeSpan time;
|
|
bool isShort;
|
|
|
|
int longFieldLen = 8;
|
|
int shortFieldLen = 5;
|
|
string sepChar;
|
|
string longFormat;
|
|
string shortFormat;
|
|
|
|
int FieldLen { get { return isShort ? shortFieldLen : longFieldLen; } }
|
|
string Format { get { return isShort ? shortFormat : longFormat; } }
|
|
|
|
/// <summary>
|
|
/// TimeChanged event, raised when the Date has changed.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// This event is raised when the <see cref="Time"/> changes.
|
|
/// </remarks>
|
|
/// <remarks>
|
|
/// The passed <see cref="EventArgs"/> is a <see cref="DateTimeEventArgs{T}"/> containing the old value, new value, and format string.
|
|
/// </remarks>
|
|
public Action<DateTimeEventArgs<TimeSpan>> TimeChanged;
|
|
|
|
/// <summary>
|
|
/// Initializes a new instance of <see cref="TimeField"/> using <see cref="LayoutStyle.Absolute"/> positioning.
|
|
/// </summary>
|
|
/// <param name="x">The x coordinate.</param>
|
|
/// <param name="y">The y coordinate.</param>
|
|
/// <param name="time">Initial time.</param>
|
|
/// <param name="isShort">If true, the seconds are hidden. Sets the <see cref="IsShortFormat"/> property.</param>
|
|
public TimeField (int x, int y, TimeSpan time, bool isShort = false) : base (x, y, isShort ? 7 : 10, "")
|
|
{
|
|
this.isShort = isShort;
|
|
Initialize (time);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Initializes a new instance of <see cref="TimeField"/> using <see cref="LayoutStyle.Computed"/> positioning.
|
|
/// </summary>
|
|
/// <param name="time">Initial time</param>
|
|
public TimeField (TimeSpan time) : base (string.Empty)
|
|
{
|
|
this.isShort = true;
|
|
Width = FieldLen + 2;
|
|
Initialize (time);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Initializes a new instance of <see cref="TimeField"/> using <see cref="LayoutStyle.Computed"/> positioning.
|
|
/// </summary>
|
|
public TimeField () : this (time: TimeSpan.MinValue) { }
|
|
|
|
void Initialize (TimeSpan time)
|
|
{
|
|
CultureInfo cultureInfo = CultureInfo.CurrentCulture;
|
|
sepChar = cultureInfo.DateTimeFormat.TimeSeparator;
|
|
longFormat = $" hh\\{sepChar}mm\\{sepChar}ss";
|
|
shortFormat = $" hh\\{sepChar}mm";
|
|
CursorPosition = 1;
|
|
Time = time;
|
|
TextChanged += TimeField_Changed;
|
|
}
|
|
|
|
void TimeField_Changed (ustring e)
|
|
{
|
|
try {
|
|
if (!TimeSpan.TryParseExact (Text.ToString ().Trim (), Format.Trim (), CultureInfo.CurrentCulture, TimeSpanStyles.None, out TimeSpan result))
|
|
Text = e;
|
|
} catch (Exception) {
|
|
Text = e;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets or sets the time of the <see cref="TimeField"/>.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// </remarks>
|
|
public TimeSpan Time {
|
|
get {
|
|
return time;
|
|
}
|
|
set {
|
|
if (ReadOnly)
|
|
return;
|
|
|
|
var oldTime = time;
|
|
time = value;
|
|
this.Text = " " + value.ToString (Format.Trim ());
|
|
var args = new DateTimeEventArgs<TimeSpan> (oldTime, value, Format);
|
|
if (oldTime != value) {
|
|
OnTimeChanged (args);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get or sets whether <see cref="TimeField"/> uses the short or long time format.
|
|
/// </summary>
|
|
public bool IsShortFormat {
|
|
get => isShort;
|
|
set {
|
|
isShort = value;
|
|
if (isShort)
|
|
Width = 7;
|
|
else
|
|
Width = 10;
|
|
var ro = ReadOnly;
|
|
if (ro)
|
|
ReadOnly = false;
|
|
SetText (Text);
|
|
ReadOnly = ro;
|
|
SetNeedsDisplay ();
|
|
}
|
|
}
|
|
|
|
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)
|
|
{
|
|
if (text.IsEmpty) {
|
|
return false;
|
|
}
|
|
|
|
ustring [] vals = text.Split (ustring.Make (sepChar));
|
|
bool isValidTime = true;
|
|
int hour = Int32.Parse (vals [0].ToString ());
|
|
int minute = Int32.Parse (vals [1].ToString ());
|
|
int second = isShort ? 0 : vals.Length > 2 ? Int32.Parse (vals [2].ToString ()) : 0;
|
|
if (hour < 0) {
|
|
isValidTime = false;
|
|
hour = 0;
|
|
vals [0] = "0";
|
|
} else if (hour > 23) {
|
|
isValidTime = false;
|
|
hour = 23;
|
|
vals [0] = "23";
|
|
}
|
|
if (minute < 0) {
|
|
isValidTime = false;
|
|
minute = 0;
|
|
vals [1] = "0";
|
|
} else if (minute > 59) {
|
|
isValidTime = false;
|
|
minute = 59;
|
|
vals [1] = "59";
|
|
}
|
|
if (second < 0) {
|
|
isValidTime = false;
|
|
second = 0;
|
|
vals [2] = "0";
|
|
} else if (second > 59) {
|
|
isValidTime = false;
|
|
second = 59;
|
|
vals [2] = "59";
|
|
}
|
|
string t = isShort ? $" {hour,2:00}{sepChar}{minute,2:00}" : $" {hour,2:00}{sepChar}{minute,2:00}{sepChar}{second,2:00}";
|
|
|
|
if (!TimeSpan.TryParseExact (t.Trim (), Format.Trim (), CultureInfo.CurrentCulture, TimeSpanStyles.None, out TimeSpan result) ||
|
|
!isValidTime)
|
|
return false;
|
|
Time = result;
|
|
return true;
|
|
}
|
|
|
|
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++;
|
|
}
|
|
|
|
///<inheritdoc/>
|
|
public override bool ProcessKey (KeyEvent kb)
|
|
{
|
|
switch (kb.Key) {
|
|
case Key.DeleteChar:
|
|
case Key.ControlD:
|
|
if (ReadOnly)
|
|
return true;
|
|
|
|
SetText ('0');
|
|
break;
|
|
|
|
case Key.Delete:
|
|
case Key.Backspace:
|
|
if (ReadOnly)
|
|
return true;
|
|
|
|
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 (ReadOnly)
|
|
return true;
|
|
|
|
if (SetText (TextModel.ToRunes (ustring.Make ((uint)kb.Key)).First ()))
|
|
IncCursorPosition ();
|
|
return true;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
///<inheritdoc/>
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Event firing method that invokes the <see cref="TimeChanged"/> event.
|
|
/// </summary>
|
|
/// <param name="args">The event arguments</param>
|
|
public virtual void OnTimeChanged (DateTimeEventArgs<TimeSpan> args)
|
|
{
|
|
TimeChanged?.Invoke (args);
|
|
}
|
|
}
|
|
} |