mirror of
https://github.com/gui-cs/Terminal.Gui.git
synced 2025-12-26 15:57:56 +01:00
Feature TextValidateField (#1230)
* Feature TextValidateField * Fix Mouse Click * Fix Mouse click on TextRegexProvider * UiCatalog fields with text alginment centered * Fix Mouse click on TextRegexProvider when right aligned * added newline to text.cs catalog * NetMaskedTextProvider - changing the mask, try to use current input. NetMaskedTextProvider - Left and Right Cursor dosen't wrap around. * Add Some TextValidateField Tests. * Add TextRegexProvider Tests * Remove unnecessary using * Tests namespace to Terminal.Gui.Views * Regex Parse exception handling * remove textmaskprovider in favor of .net maskedtextprovider * refactoring and cleaning
This commit is contained in:
committed by
GitHub
parent
b29240f362
commit
819dc291bc
610
Terminal.Gui/Views/TextValidateField.cs
Normal file
610
Terminal.Gui/Views/TextValidateField.cs
Normal file
@@ -0,0 +1,610 @@
|
||||
//
|
||||
// TextValidateField.cs: single-line text editor with validation through providers.
|
||||
//
|
||||
// Authors:
|
||||
// José Miguel Perricone (jmperricone@hotmail.com)
|
||||
//
|
||||
|
||||
using NStack;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using Terminal.Gui.TextValidateProviders;
|
||||
|
||||
namespace Terminal.Gui {
|
||||
|
||||
namespace TextValidateProviders {
|
||||
/// <summary>
|
||||
/// TextValidateField Providers Interface.
|
||||
/// All TextValidateField are created with a ITextValidateProvider.
|
||||
/// </summary>
|
||||
public interface ITextValidateProvider {
|
||||
/// <summary>
|
||||
/// Set that this provider uses a fixed width.
|
||||
/// e.g. Masked ones are fixed.
|
||||
/// </summary>
|
||||
bool Fixed { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Set Cursor position to <paramref name="pos"/>.
|
||||
/// </summary>
|
||||
/// <param name="pos"></param>
|
||||
/// <returns>Return first valid position.</returns>
|
||||
int Cursor (int pos);
|
||||
|
||||
/// <summary>
|
||||
/// First valid position before <paramref name="pos"/>.
|
||||
/// </summary>
|
||||
/// <param name="pos"></param>
|
||||
/// <returns>New cursor position if any, otherwise returns <paramref name="pos"/></returns>
|
||||
int CursorLeft (int pos);
|
||||
|
||||
/// <summary>
|
||||
/// First valid position after <paramref name="pos"/>.
|
||||
/// </summary>
|
||||
/// <param name="pos">Current position.</param>
|
||||
/// <returns>New cursor position if any, otherwise returns <paramref name="pos"/></returns>
|
||||
int CursorRight (int pos);
|
||||
|
||||
/// <summary>
|
||||
/// Find the first valid character position.
|
||||
/// </summary>
|
||||
/// <returns>New cursor position.</returns>
|
||||
int CursorStart ();
|
||||
|
||||
/// <summary>
|
||||
/// Find the last valid character position.
|
||||
/// </summary>
|
||||
/// <returns>New cursor position.</returns>
|
||||
int CursorEnd ();
|
||||
|
||||
/// <summary>
|
||||
/// Deletes the current character in <paramref name="pos"/>.
|
||||
/// </summary>
|
||||
/// <param name="pos"></param>
|
||||
/// <returns>true if the character was successfully removed, otherwise false.</returns>
|
||||
bool Delete (int pos);
|
||||
|
||||
/// <summary>
|
||||
/// Insert character <paramref name="ch"/> in position <paramref name="pos"/>.
|
||||
/// </summary>
|
||||
/// <param name="ch"></param>
|
||||
/// <param name="pos"></param>
|
||||
/// <returns>true if the character was successfully inserted, otherwise false.</returns>
|
||||
bool InsertAt (char ch, int pos);
|
||||
|
||||
/// <summary>
|
||||
/// True if the input is valid, otherwise false.
|
||||
/// </summary>
|
||||
bool IsValid { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Set the input text, and get the formatted string for display.
|
||||
/// </summary>
|
||||
ustring Text { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Mask used for validation.
|
||||
/// Not always a mask, can by a regex expression.
|
||||
/// TODO: Maybe we can change the name.
|
||||
/// </summary>
|
||||
ustring Mask { get; set; }
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
// PROVIDERS
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#region NetMaskedTextProvider
|
||||
|
||||
/// <summary>
|
||||
/// .Net MaskedTextProvider Provider for TextValidateField.
|
||||
/// <para></para>
|
||||
/// <para><a href="https://docs.microsoft.com/en-us/dotnet/api/system.componentmodel.maskedtextprovider?view=net-5.0">Wrapper around MaskedTextProvider</a></para>
|
||||
/// <para><a href="https://docs.microsoft.com/en-us/dotnet/api/system.windows.forms.maskedtextbox.mask?view=net-5.0">Masking elements</a></para>
|
||||
/// </summary>
|
||||
public class NetMaskedTextProvider : ITextValidateProvider {
|
||||
MaskedTextProvider provider;
|
||||
string text;
|
||||
|
||||
/// <summary>
|
||||
/// Empty Constructor
|
||||
/// </summary>
|
||||
public NetMaskedTextProvider () { }
|
||||
|
||||
///<inheritdoc/>
|
||||
public ustring Mask {
|
||||
get {
|
||||
return provider?.Mask;
|
||||
}
|
||||
set {
|
||||
provider = new MaskedTextProvider (value == ustring.Empty ? "&&&&&&" : value.ToString ());
|
||||
if (string.IsNullOrEmpty (text) == false) {
|
||||
provider.Set (text);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
///<inheritdoc/>
|
||||
public ustring Text {
|
||||
get {
|
||||
return provider.ToDisplayString ();
|
||||
}
|
||||
set {
|
||||
text = value.ToString ();
|
||||
provider.Set (value.ToString ());
|
||||
}
|
||||
}
|
||||
|
||||
///<inheritdoc/>
|
||||
public bool IsValid => provider.MaskCompleted;
|
||||
|
||||
///<inheritdoc/>
|
||||
public bool Fixed => true;
|
||||
|
||||
///<inheritdoc/>
|
||||
public int Cursor (int pos)
|
||||
{
|
||||
if (pos < 0) {
|
||||
return CursorStart ();
|
||||
} else if (pos > provider.Length) {
|
||||
return CursorEnd ();
|
||||
} else {
|
||||
var p = provider.FindEditPositionFrom (pos, false);
|
||||
if (p == -1) p = provider.FindEditPositionFrom (pos, true);
|
||||
return p;
|
||||
}
|
||||
}
|
||||
|
||||
///<inheritdoc/>
|
||||
public int CursorStart ()
|
||||
{
|
||||
return
|
||||
provider.IsEditPosition (0)
|
||||
? 0
|
||||
: provider.FindEditPositionFrom (0, true);
|
||||
}
|
||||
|
||||
///<inheritdoc/>
|
||||
public int CursorEnd ()
|
||||
{
|
||||
return
|
||||
provider.IsEditPosition (provider.Length - 1)
|
||||
? provider.Length - 1
|
||||
: provider.FindEditPositionFrom (provider.Length, false);
|
||||
}
|
||||
|
||||
///<inheritdoc/>
|
||||
public int CursorLeft (int pos)
|
||||
{
|
||||
var c = provider.FindEditPositionFrom (pos - 1, false);
|
||||
return c == -1 ? pos : c;
|
||||
}
|
||||
|
||||
///<inheritdoc/>
|
||||
public int CursorRight (int pos)
|
||||
{
|
||||
var c = provider.FindEditPositionFrom (pos + 1, true);
|
||||
return c == -1 ? pos : c;
|
||||
}
|
||||
|
||||
///<inheritdoc/>
|
||||
public bool Delete (int pos)
|
||||
{
|
||||
return provider.Replace (' ', pos);// .RemoveAt (pos);
|
||||
}
|
||||
|
||||
///<inheritdoc/>
|
||||
public bool InsertAt (char ch, int pos)
|
||||
{
|
||||
return provider.Replace (ch, pos);
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region TextRegexProvider
|
||||
|
||||
/// <summary>
|
||||
/// Regex Provider for TextValidateField.
|
||||
/// </summary>
|
||||
public class TextRegexProvider : ITextValidateProvider {
|
||||
Regex regex;
|
||||
List<Rune> text;
|
||||
List<Rune> mask;
|
||||
|
||||
/// <summary>
|
||||
/// Empty Constructor
|
||||
/// </summary>
|
||||
public TextRegexProvider () { }
|
||||
|
||||
///<inheritdoc/>
|
||||
public ustring Mask {
|
||||
get {
|
||||
return ustring.Make (mask);
|
||||
}
|
||||
set {
|
||||
mask = value.ToRuneList ();
|
||||
CompileMask ();
|
||||
SetupText ();
|
||||
}
|
||||
}
|
||||
|
||||
///<inheritdoc/>
|
||||
public ustring Text {
|
||||
get {
|
||||
return ustring.Make (text);
|
||||
}
|
||||
set {
|
||||
text = value != ustring.Empty ? value.ToRuneList () : null;
|
||||
SetupText ();
|
||||
}
|
||||
}
|
||||
|
||||
///<inheritdoc/>
|
||||
public bool IsValid {
|
||||
get {
|
||||
return Validate (text);
|
||||
}
|
||||
}
|
||||
|
||||
///<inheritdoc/>
|
||||
public bool Fixed => false;
|
||||
|
||||
/// <summary>
|
||||
/// When true, validates with the regex pattern on each input, preventing the input if it's not valid.
|
||||
/// </summary>
|
||||
public bool ValidateOnInput { get; set; } = true;
|
||||
|
||||
bool Validate (List<Rune> text)
|
||||
{
|
||||
var match = regex.Match (ustring.Make (text).ToString ());
|
||||
return match.Success;
|
||||
}
|
||||
|
||||
///<inheritdoc/>
|
||||
public int Cursor (int pos)
|
||||
{
|
||||
if (pos < 0) {
|
||||
return CursorStart ();
|
||||
} else if (pos >= text.Count) {
|
||||
return CursorEnd ();
|
||||
} else {
|
||||
return pos;
|
||||
}
|
||||
}
|
||||
|
||||
///<inheritdoc/>
|
||||
public int CursorStart ()
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
///<inheritdoc/>
|
||||
public int CursorEnd ()
|
||||
{
|
||||
return text.Count;
|
||||
}
|
||||
|
||||
///<inheritdoc/>
|
||||
public int CursorLeft (int pos)
|
||||
{
|
||||
if (pos > 0) {
|
||||
return pos - 1;
|
||||
}
|
||||
return pos;
|
||||
}
|
||||
|
||||
///<inheritdoc/>
|
||||
public int CursorRight (int pos)
|
||||
{
|
||||
if (pos < text.Count) {
|
||||
return pos + 1;
|
||||
}
|
||||
return pos;
|
||||
}
|
||||
|
||||
///<inheritdoc/>
|
||||
public bool Delete (int pos)
|
||||
{
|
||||
if (text.Count > 0 && pos < text.Count) {
|
||||
text.RemoveAt (pos);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
///<inheritdoc/>
|
||||
public bool InsertAt (char ch, int pos)
|
||||
{
|
||||
var aux = text.ToList ();
|
||||
aux.Insert (pos, ch);
|
||||
if (Validate (aux) || ValidateOnInput == false) {
|
||||
text.Insert (pos, ch);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void SetupText ()
|
||||
{
|
||||
if (text != null && IsValid) {
|
||||
return;
|
||||
}
|
||||
|
||||
text = new List<Rune> ();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compiles the regex pattern for validation./>
|
||||
/// </summary>
|
||||
private void CompileMask ()
|
||||
{
|
||||
regex = new Regex (ustring.Make (mask).ToString (), RegexOptions.Compiled);
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Text field that validates input through a <see cref="ITextValidateProvider"/>
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
public class TextValidateField<T> : View where T : ITextValidateProvider {
|
||||
|
||||
ITextValidateProvider provider;
|
||||
int cursorPosition = 0;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="TextValidateField{T}"/> class using <see cref="LayoutStyle.Computed"/> positioning.
|
||||
/// </summary>
|
||||
public TextValidateField () : this (ustring.Empty)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="TextValidateField{T}"></see> class using <see cref="LayoutStyle.Computed"/> positioning.
|
||||
/// </summary>
|
||||
/// <param name="mask">Mask</param>
|
||||
public TextValidateField (ustring mask) : this (mask, ustring.Empty) { }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="TextValidateField{T}"/> class using <see cref="LayoutStyle.Computed"/> positioning.
|
||||
/// </summary>
|
||||
/// <param name="mask"></param>
|
||||
/// <param name="text">Initial Value</param>
|
||||
public TextValidateField (ustring mask, ustring text) : base ()
|
||||
{
|
||||
provider = Activator.CreateInstance (typeof (T)) as ITextValidateProvider;
|
||||
|
||||
Mask = mask;
|
||||
Text = text;
|
||||
|
||||
this.Width = text == ustring.Empty ? 20 : Text.Length;
|
||||
this.Height = 1;
|
||||
this.CanFocus = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the Provider
|
||||
/// </summary>
|
||||
public T Provider => (T)provider;
|
||||
|
||||
///<inheritdoc/>
|
||||
public override bool MouseEvent (MouseEvent mouseEvent)
|
||||
{
|
||||
var c = provider.Cursor (mouseEvent.X - GetMargins (Frame.Width).left);
|
||||
if (provider.Fixed == false && TextAlignment == TextAlignment.Right && Text.Length > 0) {
|
||||
c += 1;
|
||||
}
|
||||
cursorPosition = c;
|
||||
SetFocus ();
|
||||
SetNeedsDisplay ();
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Text
|
||||
/// </summary>
|
||||
public new ustring Text {
|
||||
get {
|
||||
return provider.Text;
|
||||
}
|
||||
set {
|
||||
provider.Text = value;
|
||||
|
||||
SetNeedsDisplay ();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Mask
|
||||
/// </summary>
|
||||
public ustring Mask {
|
||||
get {
|
||||
return provider.Mask;
|
||||
}
|
||||
set {
|
||||
provider.Mask = value;
|
||||
|
||||
cursorPosition = provider.CursorStart ();
|
||||
|
||||
SetNeedsDisplay ();
|
||||
}
|
||||
}
|
||||
|
||||
///inheritdoc/>
|
||||
public override void PositionCursor ()
|
||||
{
|
||||
var (left, _) = GetMargins (Frame.Width);
|
||||
|
||||
// Fixed = true, is for inputs thar have fixed width, like masked ones.
|
||||
// Fixed = false, is for normal input.
|
||||
// When it's right-aligned and it's a normal input, the cursor behaves differently.
|
||||
if (provider.Fixed == false && TextAlignment == TextAlignment.Right) {
|
||||
Move (cursorPosition + left - 1, 0);
|
||||
} else {
|
||||
Move (cursorPosition + left, 0);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Margins for text alignment.
|
||||
/// </summary>
|
||||
/// <param name="width">Total width</param>
|
||||
/// <returns>Left and right margins</returns>
|
||||
(int left, int right) GetMargins (int width)
|
||||
{
|
||||
var count = Text.Length;
|
||||
var total = width - count;
|
||||
switch (TextAlignment) {
|
||||
case TextAlignment.Left:
|
||||
return (0, total);
|
||||
case TextAlignment.Centered:
|
||||
return (total / 2, (total / 2) + (total % 2));
|
||||
case TextAlignment.Right:
|
||||
return (total, 0);
|
||||
default:
|
||||
return (0, total);
|
||||
}
|
||||
}
|
||||
|
||||
///<inheritdoc/>
|
||||
public override void Redraw (Rect bounds)
|
||||
{
|
||||
var bgcolor = !IsValid ? Color.BrightRed : ColorScheme.Focus.Background;
|
||||
var textColor = new Attribute (ColorScheme.Focus.Foreground, bgcolor);
|
||||
|
||||
var (margin_left, margin_right) = GetMargins (bounds.Width);
|
||||
|
||||
Move (0, 0);
|
||||
|
||||
// Left Margin
|
||||
Driver.SetAttribute (textColor);
|
||||
for (int i = 0; i < margin_left; i++) {
|
||||
Driver.AddRune (' ');
|
||||
}
|
||||
|
||||
// Content
|
||||
Driver.SetAttribute (textColor);
|
||||
// Content
|
||||
for (int i = 0; i < provider.Text.Length; i++) {
|
||||
Driver.AddRune (provider.Text [i]);
|
||||
}
|
||||
|
||||
// Right Margin
|
||||
Driver.SetAttribute (textColor);
|
||||
for (int i = 0; i < margin_right; i++) {
|
||||
Driver.AddRune (' ');
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Try to move the cursor to the left.
|
||||
/// </summary>
|
||||
/// <returns>True if moved.</returns>
|
||||
bool CursorLeft ()
|
||||
{
|
||||
var current = cursorPosition;
|
||||
cursorPosition = provider.CursorLeft (cursorPosition);
|
||||
return current != cursorPosition;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Try to move the cursor to the right.
|
||||
/// </summary>
|
||||
/// <returns>True if moved.</returns>
|
||||
bool CursorRight ()
|
||||
{
|
||||
var current = cursorPosition;
|
||||
cursorPosition = provider.CursorRight (cursorPosition);
|
||||
return current != cursorPosition;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Delete char at cursor position - 1, moving the cursor.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
bool BackspaceKeyHandler ()
|
||||
{
|
||||
if (provider.Fixed == false && TextAlignment == TextAlignment.Right && cursorPosition <= 1) {
|
||||
return false;
|
||||
}
|
||||
cursorPosition = provider.CursorLeft (cursorPosition);
|
||||
provider.Delete (cursorPosition);
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Deletes char at current position.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
bool DeleteKeyHandler ()
|
||||
{
|
||||
if (provider.Fixed == false && TextAlignment == TextAlignment.Right) {
|
||||
cursorPosition = provider.CursorLeft (cursorPosition);
|
||||
}
|
||||
provider.Delete (cursorPosition);
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Moves the cursor to first char.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
bool HomeKeyHandler ()
|
||||
{
|
||||
cursorPosition = provider.CursorStart ();
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Moves the cursor to the last char.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
bool EndKeyHandler ()
|
||||
{
|
||||
cursorPosition = provider.CursorEnd ();
|
||||
return true;
|
||||
}
|
||||
|
||||
///<inheritdoc/>
|
||||
public override bool ProcessKey (KeyEvent kb)
|
||||
{
|
||||
switch (kb.Key) {
|
||||
case Key.Home: HomeKeyHandler (); break;
|
||||
case Key.End: EndKeyHandler (); break;
|
||||
case Key.Delete:
|
||||
case Key.DeleteChar: DeleteKeyHandler (); break;
|
||||
case Key.Backspace: BackspaceKeyHandler (); break;
|
||||
case Key.CursorLeft: CursorLeft (); break;
|
||||
case Key.CursorRight: CursorRight (); break;
|
||||
default:
|
||||
if (kb.Key < Key.Space || kb.Key > Key.CharMask)
|
||||
return false;
|
||||
|
||||
var key = new Rune ((uint)kb.KeyValue);
|
||||
|
||||
var inserted = provider.InsertAt ((char)key, cursorPosition);
|
||||
|
||||
if (inserted) {
|
||||
CursorRight ();
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
SetNeedsDisplay ();
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This property returns true if the input is valid.
|
||||
/// </summary>
|
||||
public virtual bool IsValid {
|
||||
get {
|
||||
return provider.IsValid;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,9 @@
|
||||
using System;
|
||||
using System.Text;
|
||||
using Terminal.Gui;
|
||||
using Terminal.Gui.TextValidateProviders;
|
||||
|
||||
|
||||
|
||||
namespace UICatalog {
|
||||
[ScenarioMetadata (Name: "Text Input Controls", Description: "Tests all text input controls")]
|
||||
@@ -101,6 +104,38 @@ namespace UICatalog {
|
||||
|
||||
_timeField.TimeChanged += TimeChanged;
|
||||
|
||||
// MaskedTextProvider
|
||||
var netProvider = new Label (".Net MaskedTextProvider [ 999 000 LLL >LLL| AAA aaa ]") {
|
||||
X = Pos.Left (dateField),
|
||||
Y = Pos.Bottom (dateField) + 1
|
||||
};
|
||||
Win.Add (netProvider);
|
||||
|
||||
var netProviderField = new TextValidateField<NetMaskedTextProvider> ("999 000 LLL >LLL| AAA aaa") {
|
||||
X = Pos.Right (netProvider) + 1,
|
||||
Y = Pos.Y (netProvider),
|
||||
Width = 40,
|
||||
TextAlignment = TextAlignment.Centered
|
||||
};
|
||||
Win.Add (netProviderField);
|
||||
|
||||
// TextRegexProvider
|
||||
var regexProvider = new Label ("Gui.cs TextRegexProvider [ ^([0-9]?[0-9]?[0-9]|1000)$ ]") {
|
||||
X = Pos.Left (netProvider),
|
||||
Y = Pos.Bottom (netProvider) + 1
|
||||
};
|
||||
Win.Add (regexProvider);
|
||||
|
||||
var regexProviderField = new TextValidateField<TextRegexProvider> ("^([0-9]?[0-9]?[0-9]|1000)$") {
|
||||
X = Pos.Right (regexProvider) + 1,
|
||||
Y = Pos.Y (regexProvider),
|
||||
Width = 40,
|
||||
TextAlignment = TextAlignment.Centered
|
||||
};
|
||||
// Access the inner Provider to configure.
|
||||
regexProviderField.Provider.ValidateOnInput = false;
|
||||
|
||||
Win.Add (regexProviderField);
|
||||
}
|
||||
|
||||
TimeField _timeField;
|
||||
@@ -109,7 +144,6 @@ namespace UICatalog {
|
||||
private void TimeChanged (DateTimeEventArgs<TimeSpan> e)
|
||||
{
|
||||
_labelMirroringTimeField.Text = _timeField.Text;
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
560
UnitTests/TextValidateFieldTests.cs
Normal file
560
UnitTests/TextValidateFieldTests.cs
Normal file
@@ -0,0 +1,560 @@
|
||||
using System.Text.RegularExpressions;
|
||||
using Terminal.Gui.TextValidateProviders;
|
||||
|
||||
using Xunit;
|
||||
|
||||
namespace Terminal.Gui.Views {
|
||||
public class TextValidateField_NET_Provider_Tests {
|
||||
public TextValidateField_NET_Provider_Tests ()
|
||||
{
|
||||
Application.Init (new FakeDriver (), new FakeMainLoop (() => FakeConsole.ReadKey (true)));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Initialized_With_Cursor_On_First_Editable_Character ()
|
||||
{
|
||||
// *
|
||||
// 0123456789
|
||||
var field = new TextValidateField<NetMaskedTextProvider> ("--(0000)--") {
|
||||
TextAlignment = TextAlignment.Centered,
|
||||
Width = 20
|
||||
};
|
||||
|
||||
field.ProcessKey (new KeyEvent (Key.D1, new KeyModifiers { }));
|
||||
|
||||
Assert.Equal ("--(1___)--", field.Text);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Input_Ilegal_Character ()
|
||||
{
|
||||
// *
|
||||
// 0123456789
|
||||
var field = new TextValidateField<NetMaskedTextProvider> ("--(0000)--") {
|
||||
TextAlignment = TextAlignment.Centered,
|
||||
Width = 20
|
||||
};
|
||||
|
||||
field.ProcessKey (new KeyEvent (Key.A, new KeyModifiers { }));
|
||||
|
||||
Assert.Equal ("--(____)--", field.Text);
|
||||
Assert.False (field.IsValid);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Home_Key_First_Editable_Character ()
|
||||
{
|
||||
// *
|
||||
// 0123456789
|
||||
var field = new TextValidateField<NetMaskedTextProvider> ("--(0000)--") {
|
||||
TextAlignment = TextAlignment.Centered,
|
||||
Width = 20
|
||||
};
|
||||
|
||||
field.ProcessKey (new KeyEvent (Key.CursorRight, new KeyModifiers { }));
|
||||
field.ProcessKey (new KeyEvent (Key.CursorRight, new KeyModifiers { }));
|
||||
field.ProcessKey (new KeyEvent (Key.Home, new KeyModifiers { }));
|
||||
|
||||
field.ProcessKey (new KeyEvent (Key.D1, new KeyModifiers { }));
|
||||
|
||||
Assert.Equal ("--(1___)--", field.Text);
|
||||
Assert.False (field.IsValid);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void End_Key_Last_Editable_Character ()
|
||||
{
|
||||
// *
|
||||
// 0123456789
|
||||
var field = new TextValidateField<NetMaskedTextProvider> ("--(0000)--") {
|
||||
TextAlignment = TextAlignment.Centered,
|
||||
Width = 20
|
||||
};
|
||||
|
||||
field.ProcessKey (new KeyEvent (Key.End, new KeyModifiers { }));
|
||||
|
||||
field.ProcessKey (new KeyEvent (Key.D1, new KeyModifiers { }));
|
||||
Assert.Equal ("--(___1)--", field.Text);
|
||||
Assert.False (field.IsValid);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Right_Key_Stops_In_Last_Editable_Character ()
|
||||
{
|
||||
// *
|
||||
// 0123456789
|
||||
var field = new TextValidateField<NetMaskedTextProvider> ("--(0000)--") {
|
||||
TextAlignment = TextAlignment.Centered,
|
||||
Width = 20
|
||||
};
|
||||
|
||||
for (int i = 0; i < 10; i++) {
|
||||
field.ProcessKey (new KeyEvent (Key.CursorRight, new KeyModifiers { }));
|
||||
}
|
||||
field.ProcessKey (new KeyEvent (Key.D1, new KeyModifiers { }));
|
||||
|
||||
Assert.Equal ("--(___1)--", field.Text);
|
||||
Assert.False (field.IsValid);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Left_Key_Stops_In_First_Editable_Character ()
|
||||
{
|
||||
// *
|
||||
// 0123456789
|
||||
var field = new TextValidateField<NetMaskedTextProvider> ("--(0000)--") {
|
||||
TextAlignment = TextAlignment.Centered,
|
||||
Width = 20
|
||||
};
|
||||
|
||||
for (int i = 0; i < 10; i++) {
|
||||
field.ProcessKey (new KeyEvent (Key.CursorLeft, new KeyModifiers { }));
|
||||
}
|
||||
field.ProcessKey (new KeyEvent (Key.D1, new KeyModifiers { }));
|
||||
|
||||
Assert.Equal ("--(1___)--", field.Text);
|
||||
Assert.False (field.IsValid);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void When_Valid_Is_Valid_True ()
|
||||
{
|
||||
// ****
|
||||
// 0123456789
|
||||
var field = new TextValidateField<NetMaskedTextProvider> ("--(0000)--") {
|
||||
TextAlignment = TextAlignment.Centered,
|
||||
Width = 20
|
||||
};
|
||||
|
||||
field.ProcessKey (new KeyEvent (Key.D1, new KeyModifiers { }));
|
||||
Assert.Equal ("--(1___)--", field.Text);
|
||||
Assert.False (field.IsValid);
|
||||
|
||||
field.ProcessKey (new KeyEvent (Key.D2, new KeyModifiers { }));
|
||||
Assert.Equal ("--(12__)--", field.Text);
|
||||
Assert.False (field.IsValid);
|
||||
|
||||
field.ProcessKey (new KeyEvent (Key.D3, new KeyModifiers { }));
|
||||
Assert.Equal ("--(123_)--", field.Text);
|
||||
Assert.False (field.IsValid);
|
||||
|
||||
field.ProcessKey (new KeyEvent (Key.D4, new KeyModifiers { }));
|
||||
Assert.Equal ("--(1234)--", field.Text);
|
||||
Assert.True (field.IsValid);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Insert_Skips_Non_Editable_Characters ()
|
||||
{
|
||||
// ** **
|
||||
// 01234567890
|
||||
var field = new TextValidateField<NetMaskedTextProvider> ("--(00-00)--") {
|
||||
TextAlignment = TextAlignment.Centered,
|
||||
Width = 20
|
||||
};
|
||||
|
||||
field.ProcessKey (new KeyEvent (Key.D1, new KeyModifiers { }));
|
||||
Assert.Equal ("--(1_-__)--", field.Text);
|
||||
Assert.False (field.IsValid);
|
||||
|
||||
field.ProcessKey (new KeyEvent (Key.D2, new KeyModifiers { }));
|
||||
Assert.Equal ("--(12-__)--", field.Text);
|
||||
Assert.False (field.IsValid);
|
||||
|
||||
field.ProcessKey (new KeyEvent (Key.D3, new KeyModifiers { }));
|
||||
Assert.Equal ("--(12-3_)--", field.Text);
|
||||
Assert.False (field.IsValid);
|
||||
|
||||
field.ProcessKey (new KeyEvent (Key.D4, new KeyModifiers { }));
|
||||
Assert.Equal ("--(12-34)--", field.Text);
|
||||
Assert.True (field.IsValid);
|
||||
}
|
||||
|
||||
|
||||
[Fact]
|
||||
public void Initial_Value_Exact_Valid ()
|
||||
{
|
||||
// ****
|
||||
// 0123456789
|
||||
var field = new TextValidateField<NetMaskedTextProvider> ("--(0000)--", "1234") {
|
||||
TextAlignment = TextAlignment.Centered,
|
||||
Width = 20
|
||||
};
|
||||
|
||||
Assert.Equal ("--(1234)--", field.Text);
|
||||
Assert.True (field.IsValid);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Initial_Value_Bigger_Than_Mask_Discarded ()
|
||||
{
|
||||
// ****
|
||||
// 0123456789
|
||||
var field = new TextValidateField<NetMaskedTextProvider> ("--(0000)--", "12345") {
|
||||
TextAlignment = TextAlignment.Centered,
|
||||
Width = 20
|
||||
};
|
||||
|
||||
Assert.Equal ("--(____)--", field.Text);
|
||||
Assert.False (field.IsValid);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Initial_Value_Smaller_Than_Mask_Accepted ()
|
||||
{
|
||||
// ****
|
||||
// 0123456789
|
||||
var field = new TextValidateField<NetMaskedTextProvider> ("--(0000)--", "123") {
|
||||
TextAlignment = TextAlignment.Centered,
|
||||
Width = 20
|
||||
};
|
||||
|
||||
Assert.Equal ("--(123_)--", field.Text);
|
||||
Assert.False (field.IsValid);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Delete_Key_Dosent_Move_Cursor ()
|
||||
{
|
||||
// ****
|
||||
// 0123456789
|
||||
var field = new TextValidateField<NetMaskedTextProvider> ("--(0000)--", "1234") {
|
||||
TextAlignment = TextAlignment.Centered,
|
||||
Width = 20
|
||||
};
|
||||
|
||||
Assert.Equal ("--(1234)--", field.Text);
|
||||
Assert.True (field.IsValid);
|
||||
|
||||
field.ProcessKey (new KeyEvent (Key.Delete, new KeyModifiers { }));
|
||||
field.ProcessKey (new KeyEvent (Key.Delete, new KeyModifiers { }));
|
||||
field.ProcessKey (new KeyEvent (Key.Delete, new KeyModifiers { }));
|
||||
|
||||
Assert.Equal ("--(_234)--", field.Text);
|
||||
Assert.False (field.IsValid);
|
||||
|
||||
field.ProcessKey (new KeyEvent (Key.CursorRight, new KeyModifiers { }));
|
||||
field.ProcessKey (new KeyEvent (Key.CursorRight, new KeyModifiers { }));
|
||||
|
||||
field.ProcessKey (new KeyEvent (Key.Delete, new KeyModifiers { }));
|
||||
field.ProcessKey (new KeyEvent (Key.Delete, new KeyModifiers { }));
|
||||
field.ProcessKey (new KeyEvent (Key.Delete, new KeyModifiers { }));
|
||||
|
||||
Assert.Equal ("--(_2_4)--", field.Text);
|
||||
Assert.False (field.IsValid);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Backspace_Key_Deletes_Previous_Character ()
|
||||
{
|
||||
// ****
|
||||
// 0123456789
|
||||
var field = new TextValidateField<NetMaskedTextProvider> ("--(0000)--", "1234") {
|
||||
TextAlignment = TextAlignment.Centered,
|
||||
Width = 20
|
||||
};
|
||||
|
||||
// Go to the end.
|
||||
field.ProcessKey (new KeyEvent (Key.End, new KeyModifiers { }));
|
||||
|
||||
field.ProcessKey (new KeyEvent (Key.Backspace, new KeyModifiers { }));
|
||||
Assert.Equal ("--(12_4)--", field.Text);
|
||||
Assert.False (field.IsValid);
|
||||
|
||||
field.ProcessKey (new KeyEvent (Key.Backspace, new KeyModifiers { }));
|
||||
Assert.Equal ("--(1__4)--", field.Text);
|
||||
Assert.False (field.IsValid);
|
||||
|
||||
field.ProcessKey (new KeyEvent (Key.Backspace, new KeyModifiers { }));
|
||||
Assert.Equal ("--(___4)--", field.Text);
|
||||
Assert.False (field.IsValid);
|
||||
|
||||
// One more
|
||||
field.ProcessKey (new KeyEvent (Key.Backspace, new KeyModifiers { }));
|
||||
Assert.Equal ("--(___4)--", field.Text);
|
||||
Assert.False (field.IsValid);
|
||||
}
|
||||
|
||||
|
||||
[Fact]
|
||||
public void Set_Text_After_Initialization ()
|
||||
{
|
||||
// ****
|
||||
// 0123456789
|
||||
var field = new TextValidateField<NetMaskedTextProvider> ("--(0000)--") {
|
||||
TextAlignment = TextAlignment.Left,
|
||||
Width = 30
|
||||
};
|
||||
|
||||
field.Text = "1234";
|
||||
|
||||
Assert.Equal ("--(1234)--", field.Text);
|
||||
Assert.True (field.IsValid);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Changing_The_Mask_Tries_To_Keep_The_Previous_Text ()
|
||||
{
|
||||
// ****
|
||||
// 0123456789
|
||||
var field = new TextValidateField<NetMaskedTextProvider> ("--(0000)--") {
|
||||
TextAlignment = TextAlignment.Left,
|
||||
Width = 30
|
||||
};
|
||||
|
||||
field.Text = "1234";
|
||||
Assert.Equal ("--(1234)--", field.Text);
|
||||
Assert.True (field.IsValid);
|
||||
|
||||
field.Mask = "--------(00000000)--------";
|
||||
Assert.Equal ("--------(1234____)--------", field.Text);
|
||||
Assert.False (field.IsValid);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MouseClick_Right_X_Greater_Than_Text_Width_Goes_To_Last_Editable_Position ()
|
||||
{
|
||||
// ****
|
||||
// 0123456789
|
||||
var field = new TextValidateField<NetMaskedTextProvider> ("--(0000)--") {
|
||||
TextAlignment = TextAlignment.Left,
|
||||
Width = 30
|
||||
};
|
||||
|
||||
field.ProcessKey (new KeyEvent (Key.D1, new KeyModifiers { }));
|
||||
|
||||
Assert.Equal ("--(1___)--", field.Text);
|
||||
Assert.False (field.IsValid);
|
||||
|
||||
field.MouseEvent (new MouseEvent () { X = 25, Flags = MouseFlags.Button1Clicked });
|
||||
|
||||
field.ProcessKey (new KeyEvent (Key.D1, new KeyModifiers { }));
|
||||
|
||||
Assert.Equal ("--(1__1)--", field.Text);
|
||||
Assert.False (field.IsValid);
|
||||
}
|
||||
}
|
||||
|
||||
public class TextValidateField_Regex_Provider_Tests {
|
||||
public TextValidateField_Regex_Provider_Tests ()
|
||||
{
|
||||
Application.Init (new FakeDriver (), new FakeMainLoop (() => FakeConsole.ReadKey (true)));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Input_Without_Validate_On_Input ()
|
||||
{
|
||||
var field = new TextValidateField<TextRegexProvider> ("^[0-9][0-9][0-9]$") {
|
||||
Width = 20
|
||||
};
|
||||
|
||||
// Let you input
|
||||
field.Provider.ValidateOnInput = false;
|
||||
|
||||
field.ProcessKey (new KeyEvent (Key.D1, new KeyModifiers { }));
|
||||
Assert.Equal ("1", field.Text);
|
||||
Assert.False (field.IsValid);
|
||||
|
||||
field.ProcessKey (new KeyEvent (Key.D2, new KeyModifiers { }));
|
||||
Assert.Equal ("12", field.Text);
|
||||
Assert.False (field.IsValid);
|
||||
|
||||
field.ProcessKey (new KeyEvent (Key.D3, new KeyModifiers { }));
|
||||
Assert.Equal ("123", field.Text);
|
||||
Assert.True (field.IsValid);
|
||||
|
||||
field.ProcessKey (new KeyEvent (Key.D4, new KeyModifiers { }));
|
||||
Assert.Equal ("1234", field.Text);
|
||||
Assert.False (field.IsValid);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Input_With_Validate_On_Input_Set_Text ()
|
||||
{
|
||||
var field = new TextValidateField<TextRegexProvider> ("^[0-9][0-9][0-9]$") {
|
||||
Width = 20
|
||||
};
|
||||
|
||||
// Input dosen't validates the pattern.
|
||||
field.ProcessKey (new KeyEvent (Key.D1, new KeyModifiers { }));
|
||||
Assert.Equal ("", field.Text);
|
||||
Assert.False (field.IsValid);
|
||||
|
||||
// Dosen't match
|
||||
field.Text = "12356";
|
||||
Assert.Equal ("", field.Text);
|
||||
Assert.False (field.IsValid);
|
||||
|
||||
// Yes.
|
||||
field.Text = "123";
|
||||
Assert.Equal ("123", field.Text);
|
||||
Assert.True (field.IsValid);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Empty_Mask_Validates_Everything ()
|
||||
{
|
||||
// Maybe it's not the right behaviour.
|
||||
|
||||
var field = new TextValidateField<TextRegexProvider> () {
|
||||
Width = 20
|
||||
};
|
||||
|
||||
field.ProcessKey (new KeyEvent (Key.D1, new KeyModifiers { }));
|
||||
Assert.Equal ("1", field.Text);
|
||||
Assert.True (field.IsValid);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Text_With_All_Charset ()
|
||||
{
|
||||
var field = new TextValidateField<TextRegexProvider> ("^[0-9][0-9][0-9]$") {
|
||||
Width = 20
|
||||
};
|
||||
|
||||
var text = "";
|
||||
for (int i = 0; i < 255; i++) {
|
||||
text += (char)i;
|
||||
}
|
||||
|
||||
field.Text = text;
|
||||
|
||||
Assert.False (field.IsValid);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Mask_With_Invalid_Pattern_Exception ()
|
||||
{
|
||||
// Regex Exception
|
||||
// Maybe it's not the right behaviour.
|
||||
|
||||
var mask = "";
|
||||
for (int i = 0; i < 255; i++) {
|
||||
mask += (char)i;
|
||||
}
|
||||
|
||||
try {
|
||||
var field = new TextValidateField<TextRegexProvider> (mask) {
|
||||
Width = 20
|
||||
};
|
||||
} catch (RegexParseException ex) {
|
||||
Assert.True (true, ex.Message);
|
||||
return;
|
||||
}
|
||||
Assert.True (false);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Home_Key_First_Editable_Character ()
|
||||
{
|
||||
// Range 0 to 1000
|
||||
// Accepts 001 too.
|
||||
var field = new TextValidateField<TextRegexProvider> ("^[0-9]?[0-9]?[0-9]|1000$") {
|
||||
Width = 20
|
||||
};
|
||||
|
||||
field.ProcessKey (new KeyEvent (Key.D1, new KeyModifiers { }));
|
||||
field.ProcessKey (new KeyEvent (Key.D0, new KeyModifiers { }));
|
||||
field.ProcessKey (new KeyEvent (Key.D0, new KeyModifiers { }));
|
||||
field.ProcessKey (new KeyEvent (Key.D0, new KeyModifiers { }));
|
||||
|
||||
Assert.Equal ("1000", field.Text);
|
||||
Assert.True (field.IsValid);
|
||||
|
||||
// HOME KEY
|
||||
field.ProcessKey (new KeyEvent (Key.Home, new KeyModifiers { }));
|
||||
|
||||
// DELETE
|
||||
field.ProcessKey (new KeyEvent (Key.Delete, new KeyModifiers { }));
|
||||
|
||||
Assert.Equal ("000", field.Text);
|
||||
Assert.True (field.IsValid);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void End_Key_End_Of_Input ()
|
||||
{
|
||||
// Exactly 5 numbers
|
||||
var field = new TextValidateField<TextRegexProvider> ("^[0-9]{5}$") {
|
||||
Width = 20
|
||||
};
|
||||
|
||||
field.Provider.ValidateOnInput = false;
|
||||
|
||||
for (int i = 0; i < 4; i++) {
|
||||
field.ProcessKey (new KeyEvent (Key.D0, new KeyModifiers { }));
|
||||
}
|
||||
|
||||
Assert.Equal ("0000", field.Text);
|
||||
Assert.False (field.IsValid);
|
||||
|
||||
// HOME KEY
|
||||
field.ProcessKey (new KeyEvent (Key.Home, new KeyModifiers { }));
|
||||
|
||||
// END KEY
|
||||
field.ProcessKey (new KeyEvent (Key.End, new KeyModifiers { }));
|
||||
|
||||
// Insert 9
|
||||
field.ProcessKey (new KeyEvent (Key.D9, new KeyModifiers { }));
|
||||
|
||||
Assert.Equal ("00009", field.Text);
|
||||
Assert.True (field.IsValid);
|
||||
|
||||
// Insert 9
|
||||
field.ProcessKey (new KeyEvent (Key.D9, new KeyModifiers { }));
|
||||
|
||||
Assert.Equal ("000099", field.Text);
|
||||
Assert.False (field.IsValid);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Right_Key_Stops_At_End_And_Insert ()
|
||||
{
|
||||
var field = new TextValidateField<TextRegexProvider> ("^[0-9][0-9][0-9]$") {
|
||||
TextAlignment = TextAlignment.Centered,
|
||||
Width = 20
|
||||
};
|
||||
field.Provider.ValidateOnInput = false;
|
||||
|
||||
field.Text = "123";
|
||||
|
||||
for (int i = 0; i < 10; i++) {
|
||||
field.ProcessKey (new KeyEvent (Key.CursorRight, new KeyModifiers { }));
|
||||
}
|
||||
|
||||
Assert.Equal ("123", field.Text);
|
||||
Assert.True (field.IsValid);
|
||||
|
||||
// Insert 4
|
||||
field.ProcessKey (new KeyEvent (Key.D4, new KeyModifiers { }));
|
||||
|
||||
Assert.Equal ("1234", field.Text);
|
||||
Assert.False (field.IsValid);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Left_Key_Stops_At_Start_And_Insert ()
|
||||
{
|
||||
var field = new TextValidateField<TextRegexProvider> ("^[0-9][0-9][0-9]$") {
|
||||
TextAlignment = TextAlignment.Centered,
|
||||
Width = 20
|
||||
};
|
||||
field.Provider.ValidateOnInput = false;
|
||||
|
||||
field.Text = "123";
|
||||
|
||||
for (int i = 0; i < 10; i++) {
|
||||
field.ProcessKey (new KeyEvent (Key.CursorLeft, new KeyModifiers { }));
|
||||
}
|
||||
|
||||
Assert.Equal ("123", field.Text);
|
||||
Assert.True (field.IsValid);
|
||||
|
||||
// Insert 4
|
||||
field.ProcessKey (new KeyEvent (Key.D4, new KeyModifiers { }));
|
||||
|
||||
Assert.Equal ("4123", field.Text);
|
||||
Assert.False (field.IsValid);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user