diff --git a/Terminal.Gui/Views/TextValidateField.cs b/Terminal.Gui/Views/TextValidateField.cs
new file mode 100644
index 000000000..942683a5a
--- /dev/null
+++ b/Terminal.Gui/Views/TextValidateField.cs
@@ -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 {
+ ///
+ /// TextValidateField Providers Interface.
+ /// All TextValidateField are created with a ITextValidateProvider.
+ ///
+ public interface ITextValidateProvider {
+ ///
+ /// Set that this provider uses a fixed width.
+ /// e.g. Masked ones are fixed.
+ ///
+ bool Fixed { get; }
+
+ ///
+ /// Set Cursor position to .
+ ///
+ ///
+ /// Return first valid position.
+ int Cursor (int pos);
+
+ ///
+ /// First valid position before .
+ ///
+ ///
+ /// New cursor position if any, otherwise returns
+ int CursorLeft (int pos);
+
+ ///
+ /// First valid position after .
+ ///
+ /// Current position.
+ /// New cursor position if any, otherwise returns
+ int CursorRight (int pos);
+
+ ///
+ /// Find the first valid character position.
+ ///
+ /// New cursor position.
+ int CursorStart ();
+
+ ///
+ /// Find the last valid character position.
+ ///
+ /// New cursor position.
+ int CursorEnd ();
+
+ ///
+ /// Deletes the current character in .
+ ///
+ ///
+ /// true if the character was successfully removed, otherwise false.
+ bool Delete (int pos);
+
+ ///
+ /// Insert character in position .
+ ///
+ ///
+ ///
+ /// true if the character was successfully inserted, otherwise false.
+ bool InsertAt (char ch, int pos);
+
+ ///
+ /// True if the input is valid, otherwise false.
+ ///
+ bool IsValid { get; }
+
+ ///
+ /// Set the input text, and get the formatted string for display.
+ ///
+ ustring Text { get; set; }
+
+ ///
+ /// Mask used for validation.
+ /// Not always a mask, can by a regex expression.
+ /// TODO: Maybe we can change the name.
+ ///
+ ustring Mask { get; set; }
+ }
+
+ //////////////////////////////////////////////////////////////////////////////
+ // PROVIDERS
+ //////////////////////////////////////////////////////////////////////////////
+
+ #region NetMaskedTextProvider
+
+ ///
+ /// .Net MaskedTextProvider Provider for TextValidateField.
+ ///
+ /// Wrapper around MaskedTextProvider
+ /// Masking elements
+ ///
+ public class NetMaskedTextProvider : ITextValidateProvider {
+ MaskedTextProvider provider;
+ string text;
+
+ ///
+ /// Empty Constructor
+ ///
+ public NetMaskedTextProvider () { }
+
+ ///
+ public ustring Mask {
+ get {
+ return provider?.Mask;
+ }
+ set {
+ provider = new MaskedTextProvider (value == ustring.Empty ? "&&&&&&" : value.ToString ());
+ if (string.IsNullOrEmpty (text) == false) {
+ provider.Set (text);
+ }
+ }
+ }
+
+ ///
+ public ustring Text {
+ get {
+ return provider.ToDisplayString ();
+ }
+ set {
+ text = value.ToString ();
+ provider.Set (value.ToString ());
+ }
+ }
+
+ ///
+ public bool IsValid => provider.MaskCompleted;
+
+ ///
+ public bool Fixed => true;
+
+ ///
+ 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;
+ }
+ }
+
+ ///
+ public int CursorStart ()
+ {
+ return
+ provider.IsEditPosition (0)
+ ? 0
+ : provider.FindEditPositionFrom (0, true);
+ }
+
+ ///
+ public int CursorEnd ()
+ {
+ return
+ provider.IsEditPosition (provider.Length - 1)
+ ? provider.Length - 1
+ : provider.FindEditPositionFrom (provider.Length, false);
+ }
+
+ ///
+ public int CursorLeft (int pos)
+ {
+ var c = provider.FindEditPositionFrom (pos - 1, false);
+ return c == -1 ? pos : c;
+ }
+
+ ///
+ public int CursorRight (int pos)
+ {
+ var c = provider.FindEditPositionFrom (pos + 1, true);
+ return c == -1 ? pos : c;
+ }
+
+ ///
+ public bool Delete (int pos)
+ {
+ return provider.Replace (' ', pos);// .RemoveAt (pos);
+ }
+
+ ///
+ public bool InsertAt (char ch, int pos)
+ {
+ return provider.Replace (ch, pos);
+ }
+ }
+ #endregion
+
+ #region TextRegexProvider
+
+ ///
+ /// Regex Provider for TextValidateField.
+ ///
+ public class TextRegexProvider : ITextValidateProvider {
+ Regex regex;
+ List text;
+ List mask;
+
+ ///
+ /// Empty Constructor
+ ///
+ public TextRegexProvider () { }
+
+ ///
+ public ustring Mask {
+ get {
+ return ustring.Make (mask);
+ }
+ set {
+ mask = value.ToRuneList ();
+ CompileMask ();
+ SetupText ();
+ }
+ }
+
+ ///
+ public ustring Text {
+ get {
+ return ustring.Make (text);
+ }
+ set {
+ text = value != ustring.Empty ? value.ToRuneList () : null;
+ SetupText ();
+ }
+ }
+
+ ///
+ public bool IsValid {
+ get {
+ return Validate (text);
+ }
+ }
+
+ ///
+ public bool Fixed => false;
+
+ ///
+ /// When true, validates with the regex pattern on each input, preventing the input if it's not valid.
+ ///
+ public bool ValidateOnInput { get; set; } = true;
+
+ bool Validate (List text)
+ {
+ var match = regex.Match (ustring.Make (text).ToString ());
+ return match.Success;
+ }
+
+ ///
+ public int Cursor (int pos)
+ {
+ if (pos < 0) {
+ return CursorStart ();
+ } else if (pos >= text.Count) {
+ return CursorEnd ();
+ } else {
+ return pos;
+ }
+ }
+
+ ///
+ public int CursorStart ()
+ {
+ return 0;
+ }
+
+ ///
+ public int CursorEnd ()
+ {
+ return text.Count;
+ }
+
+ ///
+ public int CursorLeft (int pos)
+ {
+ if (pos > 0) {
+ return pos - 1;
+ }
+ return pos;
+ }
+
+ ///
+ public int CursorRight (int pos)
+ {
+ if (pos < text.Count) {
+ return pos + 1;
+ }
+ return pos;
+ }
+
+ ///
+ public bool Delete (int pos)
+ {
+ if (text.Count > 0 && pos < text.Count) {
+ text.RemoveAt (pos);
+ }
+ return true;
+ }
+
+ ///
+ 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 ();
+ }
+
+ ///
+ /// Compiles the regex pattern for validation./>
+ ///
+ private void CompileMask ()
+ {
+ regex = new Regex (ustring.Make (mask).ToString (), RegexOptions.Compiled);
+ }
+ }
+ #endregion
+ }
+
+ ///
+ /// Text field that validates input through a
+ ///
+ ///
+ public class TextValidateField : View where T : ITextValidateProvider {
+
+ ITextValidateProvider provider;
+ int cursorPosition = 0;
+
+ ///
+ /// Initializes a new instance of the class using positioning.
+ ///
+ public TextValidateField () : this (ustring.Empty)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class using positioning.
+ ///
+ /// Mask
+ public TextValidateField (ustring mask) : this (mask, ustring.Empty) { }
+
+ ///
+ /// Initializes a new instance of the class using positioning.
+ ///
+ ///
+ /// Initial Value
+ 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;
+ }
+
+ ///
+ /// Get the Provider
+ ///
+ public T Provider => (T)provider;
+
+ ///
+ 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;
+ }
+
+ ///
+ /// Text
+ ///
+ public new ustring Text {
+ get {
+ return provider.Text;
+ }
+ set {
+ provider.Text = value;
+
+ SetNeedsDisplay ();
+ }
+ }
+
+ ///
+ /// Mask
+ ///
+ 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);
+ }
+ }
+
+ ///
+ /// Margins for text alignment.
+ ///
+ /// Total width
+ /// Left and right margins
+ (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);
+ }
+ }
+
+ ///
+ 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 (' ');
+ }
+ }
+
+ ///
+ /// Try to move the cursor to the left.
+ ///
+ /// True if moved.
+ bool CursorLeft ()
+ {
+ var current = cursorPosition;
+ cursorPosition = provider.CursorLeft (cursorPosition);
+ return current != cursorPosition;
+ }
+
+ ///
+ /// Try to move the cursor to the right.
+ ///
+ /// True if moved.
+ bool CursorRight ()
+ {
+ var current = cursorPosition;
+ cursorPosition = provider.CursorRight (cursorPosition);
+ return current != cursorPosition;
+ }
+
+ ///
+ /// Delete char at cursor position - 1, moving the cursor.
+ ///
+ ///
+ bool BackspaceKeyHandler ()
+ {
+ if (provider.Fixed == false && TextAlignment == TextAlignment.Right && cursorPosition <= 1) {
+ return false;
+ }
+ cursorPosition = provider.CursorLeft (cursorPosition);
+ provider.Delete (cursorPosition);
+ return true;
+ }
+
+ ///
+ /// Deletes char at current position.
+ ///
+ ///
+ bool DeleteKeyHandler ()
+ {
+ if (provider.Fixed == false && TextAlignment == TextAlignment.Right) {
+ cursorPosition = provider.CursorLeft (cursorPosition);
+ }
+ provider.Delete (cursorPosition);
+ return true;
+ }
+
+ ///
+ /// Moves the cursor to first char.
+ ///
+ ///
+ bool HomeKeyHandler ()
+ {
+ cursorPosition = provider.CursorStart ();
+ return true;
+ }
+
+ ///
+ /// Moves the cursor to the last char.
+ ///
+ ///
+ bool EndKeyHandler ()
+ {
+ cursorPosition = provider.CursorEnd ();
+ return true;
+ }
+
+ ///
+ 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;
+ }
+
+ ///
+ /// This property returns true if the input is valid.
+ ///
+ public virtual bool IsValid {
+ get {
+ return provider.IsValid;
+ }
+ }
+ }
+}
diff --git a/UICatalog/Scenarios/Text.cs b/UICatalog/Scenarios/Text.cs
index 86f505a14..c05555aea 100644
--- a/UICatalog/Scenarios/Text.cs
+++ b/UICatalog/Scenarios/Text.cs
@@ -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 ("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 ("^([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 e)
{
_labelMirroringTimeField.Text = _timeField.Text;
-
}
}
-}
\ No newline at end of file
+}
diff --git a/UnitTests/TextValidateFieldTests.cs b/UnitTests/TextValidateFieldTests.cs
new file mode 100644
index 000000000..536847495
--- /dev/null
+++ b/UnitTests/TextValidateFieldTests.cs
@@ -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 ("--(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 ("--(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 ("--(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 ("--(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 ("--(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 ("--(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 ("--(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 ("--(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 ("--(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 ("--(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 ("--(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 ("--(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 ("--(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 ("--(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 ("--(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 ("--(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 ("^[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 ("^[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 () {
+ 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 ("^[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 (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 ("^[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 ("^[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 ("^[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 ("^[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);
+ }
+ }
+}
\ No newline at end of file