The Labs \ Source Viewer \ SSCLI \ System.ComponentModel \ CharDescriptor

  1. //------------------------------------------------------------------------------
  2. // <copyright file="TextBoxBase.cs" company="Microsoft">
  3. //
  4. // Copyright (c) 2006 Microsoft Corporation. All rights reserved.
  5. //
  6. // The use and distribution terms for this software are contained in the file
  7. // named license.txt, which can be found in the root of this distribution.
  8. // By using this software in any fashion, you are agreeing to be bound by the
  9. // terms of this license.
  10. //
  11. // You must not remove this notice, or any other, from this software.
  12. //
  13. // </copyright>
  14. //------------------------------------------------------------------------------
  15. namespace System.ComponentModel
  16. {
  17.     using System;
  18.     using System.Collections.Generic;
  19.     using System.Collections.Specialized;
  20.     using System.Diagnostics;
  21.     using System.Globalization;
  22.     using System.Security.Permissions;
  23.     using System.Text;
  24.    
  25.     /// <devdoc>
  26.     /// Provides functionality for formatting a test string against a mask string.
  27.     /// MaskedTextProvider is stateful, it keeps information about the input characters so
  28.     /// multiple call to Add/Remove will work in the same buffer.
  29.     /// Most of the operations are performed on a virtual string containing the input characters as opposed
  30.     /// to the test string itself, since mask literals cannot be modified (i.e: replacing on a literal position
  31.     /// will actually replace on the nearest edit position forward).
  32.     /// </devdoc>
  33.     [HostProtection(SharedState = true)]
  34.     public class MaskedTextProvider : ICloneable
  35.     {
  36.         ///
  37.         /// Some concept definitions:
  38.         ///
  39.         /// 'mask' : A string representing the mask associated with an instance of this class.
  40.         /// 'test string' : A string representing the user's text formatted as specified by the mask.
  41.         /// 'virtual text' : The characters entered by the user to be converted into the 'test string'.
  42.         /// no buffer exists to hold them since they're stored in the test string but
  43.         /// we keep an array with their position in the test string for fast access.
  44.         /// 'text indexer' : An array which values point to 'edit char' positions in the test string and
  45.         /// indexes correspond to the position in the user's text.
  46.         /// 'char descriptor' : A structure describing a char constraints as specified in the mask plus some
  47.         /// other info.
  48.         /// 'string descriptor': An array of char descriptor objects describing the chars in the 'test string',
  49.         /// the indexes of this array represent the position of the chars in the string.
  50.        
  51.        
  52.         /// <devdoc>
  53.         /// Char case conversion type used when '>' (subsequent chars to upper case) or '<' (subsequent chars to lower case)
  54.         /// are specified in the mask.
  55.         /// </devdoc>
  56.         private enum CaseConversion
  57.         {
  58.             None,
  59.             ToLower,
  60.             ToUpper
  61.         }
  62.        
  63.        
  64.         /// <devdoc>
  65.         /// Type of the characters in the test string according to the mask language.
  66.         /// </devdoc>
  67.         [Flags()]
  68.         private enum CharType
  69.         {
  70.             EditOptional = 1,
  71.             // editable char ('#', '9', 'A', 'a', etc) optional.
  72.             EditRequired = 2,
  73.             // editable char ('#', '9', 'A', 'a', etc) required.
  74.             Separator = 4,
  75.             // separator char ('.', ',', ':', '$').
  76.             Literal = 8,
  77.             // literal char ('\\', '-', etc)
  78.             Modifier = 16
  79.             // char modifier ('>', '<')
  80.         }
  81.        
  82.         /// <devdoc>
  83.         /// This structure describes some constraints and properties of a character in the test string, as specified
  84.         /// in the mask.
  85.         /// </devdoc>
  86.         private class CharDescriptor
  87.         {
  88.             // The position the character holds in the mask string. Required for testing the character against the mask.
  89.             public int MaskPosition;
  90.            
  91.             // The char case conversion specified in the mask. Required for formatting the string when requested.
  92.             public CaseConversion CaseConversion;
  93.            
  94.             // The char type according to the mask language indentifiers. (Separator, Editable char...).
  95.             // Required for validating the input char.
  96.             public CharType CharType;
  97.            
  98.             // Specifies whether the editable char has been assigned a value. Meaningful to edit chars only.
  99.             public bool IsAssigned;
  100.            
  101.             // constructors.
  102.             public CharDescriptor(int maskPos, CharType charType)
  103.             {
  104.                 this.MaskPosition = maskPos;
  105.                 this.CharType = charType;
  106.             }
  107.            
  108.             public override string ToString()
  109.             {
  110.                 return String.Format(CultureInfo.InvariantCulture, "MaskPosition[{0}] <CaseConversion.{1}><CharType.{2}><IsAssigned: {3}", this.MaskPosition, this.CaseConversion, this.CharType, this.IsAssigned);
  111.             }
  112.         }
  113.        
  114.         //// class data.
  115.        
  116.         const char spaceChar = ' ';
  117.         const char defaultPromptChar = '_';
  118.         const char nullPasswordChar = '\0';
  119.         const bool defaultAllowPrompt = true;
  120.         const int invalidIndex = -1;
  121.         const byte editAny = 0;
  122.         const byte editUnassigned = 1;
  123.         const byte editAssigned = 2;
  124.         const bool forward = true;
  125.         const bool backward = false;
  126.        
  127.         // Bit masks for bool properties.
  128.         static int ASCII_ONLY = BitVector32.CreateMask();
  129.         static int ALLOW_PROMPT_AS_INPUT = BitVector32.CreateMask(ASCII_ONLY);
  130.         static int INCLUDE_PROMPT = BitVector32.CreateMask(ALLOW_PROMPT_AS_INPUT);
  131.         static int INCLUDE_LITERALS = BitVector32.CreateMask(INCLUDE_PROMPT);
  132.         static int RESET_ON_PROMPT = BitVector32.CreateMask(INCLUDE_LITERALS);
  133.         static int RESET_ON_LITERALS = BitVector32.CreateMask(RESET_ON_PROMPT);
  134.         static int SKIP_SPACE = BitVector32.CreateMask(RESET_ON_LITERALS);
  135.        
  136.         // Type cached to speed up cloning of this object.
  137.         static Type maskTextProviderType = typeof(MaskedTextProvider);
  138.        
  139.         //// Instance data.
  140.        
  141.         // Bit vector to represent bool variables.
  142.         private BitVector32 flagState;
  143.        
  144.         // Used to obtained localized placeholder chars (date separator for instance).
  145.         private CultureInfo culture;
  146.        
  147.         // the formatted string.
  148.         private StringBuilder testString;
  149.        
  150.         // the number of assigned edit chars.
  151.         private int assignedCharCount;
  152.        
  153.         // the number of assigned required edit chars.
  154.         private int requiredCharCount;
  155.        
  156.         // the number of required edit positions in the test string.
  157.         private int requiredEditChars;
  158.        
  159.         // the number of optional edit positions in the test string.
  160.         private int optionalEditChars;
  161.        
  162.         // Properties backend fields (see corresponding property for info).
  163.         private string mask;
  164.         private char passwordChar;
  165.         private char promptChar;
  166.        
  167.         // We maintain an array (string descriptor table) of CharDescriptor elements describing the characters in the
  168.         // test string, as specified in the mask. It allows us to access character information in constant time since
  169.         // we don't have to traverse the mask or test string whenever we need that information.
  170.         private List<CharDescriptor> stringDescriptor;
  171.        
  172.         ////// Construction API
  173.        
  174.         /// <devdoc>
  175.         /// Creates a MaskedTextProvider object from the specified mask.
  176.         /// </devdoc>
  177.         public MaskedTextProvider(string mask) : this(mask, null, defaultAllowPrompt, defaultPromptChar, nullPasswordChar, false)
  178.         {
  179.         }
  180.        
  181.         /// <devdoc>
  182.         /// Creates a MaskedTextProvider object from the specified mask.
  183.         /// 'restrictToAscii' specifies whether the input characters should be restricted to ASCII characters only.
  184.         /// </devdoc>
  185.         public MaskedTextProvider(string mask, bool restrictToAscii) : this(mask, null, defaultAllowPrompt, defaultPromptChar, nullPasswordChar, restrictToAscii)
  186.         {
  187.         }
  188.        
  189.         /// <devdoc>
  190.         /// Creates a MaskedTextProvider object from the specified mask.
  191.         /// 'culture' is used to set the separator characters to the correspondig locale character; if null, the current
  192.         /// culture is used.
  193.         /// </devdoc>
  194.         public MaskedTextProvider(string mask, CultureInfo culture) : this(mask, culture, defaultAllowPrompt, defaultPromptChar, nullPasswordChar, false)
  195.         {
  196.         }
  197.        
  198.         /// <devdoc>
  199.         /// Creates a MaskedTextProvider object from the specified mask.
  200.         /// 'culture' is used to set the separator characters to the correspondig locale character; if null, the current
  201.         /// culture is used.
  202.         /// 'restrictToAscii' specifies whether the input characters should be restricted to ASCII characters only.
  203.         /// </devdoc>
  204.         public MaskedTextProvider(string mask, CultureInfo culture, bool restrictToAscii) : this(mask, culture, defaultAllowPrompt, defaultPromptChar, nullPasswordChar, restrictToAscii)
  205.         {
  206.         }
  207.        
  208.         /// <devdoc>
  209.         /// Creates a MaskedTextProvider object from the specified mask .
  210.         /// 'passwordChar' specifies the character to be used in the password string.
  211.         /// 'allowPromptAsInput' specifies whether the prompt character should be accepted as a valid input or not.
  212.         /// </devdoc>
  213.         public MaskedTextProvider(string mask, char passwordChar, bool allowPromptAsInput) : this(mask, null, allowPromptAsInput, defaultPromptChar, passwordChar, false)
  214.         {
  215.         }
  216.        
  217.         /// <devdoc>
  218.         /// Creates a MaskedTextProvider object from the specified mask .
  219.         /// 'passwordChar' specifies the character to be used in the password string.
  220.         /// 'allowPromptAsInput' specifies whether the prompt character should be accepted as a valid input or not.
  221.         /// </devdoc>
  222.         public MaskedTextProvider(string mask, CultureInfo culture, char passwordChar, bool allowPromptAsInput) : this(mask, culture, allowPromptAsInput, defaultPromptChar, passwordChar, false)
  223.         {
  224.         }
  225.        
  226.         /// <devdoc>
  227.         /// Creates a MaskedTextProvider object from the specified mask.
  228.         /// 'culture' is used to set the separator characters to the correspondig locale character; if null, the current
  229.         /// culture is used.
  230.         /// 'allowPromptAsInput' specifies whether the prompt character should be accepted as a valid input or not.
  231.         /// 'promptChar' specifies the character to be used for the prompt.
  232.         /// 'passwordChar' specifies the character to be used in the password string.
  233.         /// 'restrictToAscii' specifies whether the input characters should be restricted to ASCII characters only.
  234.         /// </devdoc>
  235.         public MaskedTextProvider(string mask, CultureInfo culture, bool allowPromptAsInput, char promptChar, char passwordChar, bool restrictToAscii)
  236.         {
  237.             if (string.IsNullOrEmpty(mask)) {
  238.                 throw new ArgumentException(SR.GetString(SR.MaskedTextProviderMaskNullOrEmpty), "mask");
  239.             }
  240.            
  241.             foreach (char c in mask) {
  242.                 if (!IsPrintableChar(c)) {
  243.                     throw new ArgumentException(SR.GetString(SR.MaskedTextProviderMaskInvalidChar));
  244.                 }
  245.             }
  246.            
  247.             if (culture == null) {
  248.                 culture = CultureInfo.CurrentCulture;
  249.             }
  250.            
  251.             this.flagState = new BitVector32();
  252.            
  253.             // read only property-backend fields.
  254.            
  255.             this.mask = mask;
  256.             this.promptChar = promptChar;
  257.             this.passwordChar = passwordChar;
  258.            
  259.             //Neutral cultures cannot be queried for culture-specific information.
  260.             if (culture.IsNeutralCulture) {
  261.                 // find the first specific (non-neutral) culture that contains country specific info.
  262.                 foreach (CultureInfo tempCulture in CultureInfo.GetCultures(CultureTypes.SpecificCultures)) {
  263.                     if (culture.Equals(tempCulture.Parent)) {
  264.                         this.culture = tempCulture;
  265.                         break;
  266.                     }
  267.                 }
  268.                
  269.                 // Last resort use invariant culture.
  270.                 if (this.culture == null) {
  271.                     this.culture = CultureInfo.InvariantCulture;
  272.                 }
  273.             }
  274.             else {
  275.                 this.culture = culture;
  276.             }
  277.            
  278.             if (!this.culture.IsReadOnly) {
  279.                 this.culture = CultureInfo.ReadOnly(this.culture);
  280.             }
  281.            
  282.             this.flagState[ALLOW_PROMPT_AS_INPUT] = allowPromptAsInput;
  283.             this.flagState[ASCII_ONLY] = restrictToAscii;
  284.            
  285.             // set default values for read/write properties.
  286.            
  287.             this.flagState[INCLUDE_PROMPT] = false;
  288.             this.flagState[INCLUDE_LITERALS] = true;
  289.             this.flagState[RESET_ON_PROMPT] = true;
  290.             this.flagState[SKIP_SPACE] = true;
  291.             this.flagState[RESET_ON_LITERALS] = true;
  292.            
  293.             Initialize();
  294.         }
  295.        
  296.         /// <devdoc>
  297.         /// Initializes the test string according to the mask and populates the character descirptor table
  298.         /// (stringDescriptor).
  299.         /// </devdoc>
  300.         private void Initialize()
  301.         {
  302.             this.testString = new StringBuilder();
  303.             this.stringDescriptor = new List<CharDescriptor>();
  304.            
  305.             CaseConversion caseConversion = CaseConversion.None;
  306.             // The conversion specified in the mask.
  307.             bool escapedChar = false;
  308.             // indicates the current char is to be escaped.
  309.             int testPosition = 0;
  310.             // the position of the char in the test string.
  311.             CharType charType = CharType.Literal;
  312.             // the mask language char type.
  313.             char ch;
  314.             // the char under test.
  315.             string locSymbol = string.Empty;
  316.             // the locale symbol corresponding to a separator in the mask.
  317.             // in some cultures a symbol is represented with more than one
  318.             // char, for instance '$' for en-JA is '$J'.
  319.             //
  320.             // Traverse the mask to generate the test string and the string descriptor table so we don't have
  321.             // to traverse those strings anymore.
  322.             //
  323.             for (int maskPos = 0; maskPos < this.mask.Length; maskPos++) {
  324.                 ch = this.mask[maskPos];
  325.                 // if false treat the char as literal.
  326.                 if (!escapedChar) {
  327.                     switch (ch) {
  328.                         case '.':
  329.                             //
  330.                             // Mask language placeholders.
  331.                             // set the corresponding localized char to be added to the test string.
  332.                             //
  333.                             // decimal separator.
  334.                             locSymbol = this.culture.NumberFormat.NumberDecimalSeparator;
  335.                             charType = CharType.Separator;
  336.                             break;
  337.                         case ',':
  338.                            
  339.                             // thousands separator.
  340.                             locSymbol = this.culture.NumberFormat.NumberGroupSeparator;
  341.                             charType = CharType.Separator;
  342.                             break;
  343.                         case ':':
  344.                            
  345.                             // time separator.
  346.                             locSymbol = this.culture.DateTimeFormat.TimeSeparator;
  347.                             charType = CharType.Separator;
  348.                             break;
  349.                         case '/':
  350.                            
  351.                             // date separator.
  352.                             locSymbol = this.culture.DateTimeFormat.DateSeparator;
  353.                             charType = CharType.Separator;
  354.                             break;
  355.                         case '$':
  356.                            
  357.                             // currency symbol.
  358.                             locSymbol = this.culture.NumberFormat.CurrencySymbol;
  359.                             charType = CharType.Separator;
  360.                             break;
  361.                         case '<':
  362.                            
  363.                             //
  364.                             // Mask language modifiers.
  365.                             // StringDescriptor won't have an entry for modifiers, the modified character
  366.                             // descriptor contains an entry for case conversion that is set accordingly.
  367.                             // Just set the appropriate flag for the characters that follow and continue.
  368.                             //
  369.                             // convert all chars that follow to lowercase.
  370.                             caseConversion = CaseConversion.ToLower;
  371.                             continue;
  372.                         case '>':
  373.                            
  374.                             // convert all chars that follow to uppercase.
  375.                             caseConversion = CaseConversion.ToUpper;
  376.                             continue;
  377.                         case '|':
  378.                            
  379.                             // no convertion performed on the chars that follow.
  380.                             caseConversion = CaseConversion.None;
  381.                             continue;
  382.                         case '\\':
  383.                            
  384.                             // escape char - next will be a literal.
  385.                             escapedChar = true;
  386.                             charType = CharType.Literal;
  387.                             continue;
  388.                         case '0':
  389.                         case 'L':
  390.                         case '&':
  391.                         case 'A':
  392.                            
  393.                             //
  394.                             // Mask language edit identifiers (#, 9, &, C, A, a, ?).
  395.                             // Populate a CharDescriptor structure desrcribing the editable char corresponding to this
  396.                             // identifier.
  397.                             //
  398.                             // digit required.
  399.                             // letter required.
  400.                             // any character required.
  401.                             // alphanumeric (letter or digit) required.
  402.                             this.requiredEditChars++;
  403.                             ch = this.promptChar;
  404.                             // replace edit identifier with prompt.
  405.                             charType = CharType.EditRequired;
  406.                             // set char as editable.
  407.                             break;
  408.                         case '?':
  409.                         case '9':
  410.                         case '#':
  411.                         case 'C':
  412.                         case 'a':
  413.                            
  414.                             // letter optional (space OK).
  415.                             // digit optional (space OK).
  416.                             // digit or plus/minus sign optional (space OK).
  417.                             // any character optional (space OK).
  418.                             // alphanumeric (letter or digit) optional.
  419.                             this.optionalEditChars++;
  420.                             ch = this.promptChar;
  421.                             // replace edit identifier with prompt.
  422.                             charType = CharType.EditOptional;
  423.                             // set char as editable.
  424.                             break;
  425.                         default:
  426.                            
  427.                             //
  428.                             // Literals just break so they're added to the test string.
  429.                             //
  430.                             charType = CharType.Literal;
  431.                             break;
  432.                     }
  433.                 }
  434.                 else {
  435.                     escapedChar = false;
  436.                     // reset flag since the escaped char is now going to be added to the test string.
  437.                 }
  438.                
  439.                 // Populate a character descriptor for the current character (or loc symbol).
  440.                 CharDescriptor chDex = new CharDescriptor(maskPos, charType);
  441.                
  442.                 if (IsEditPosition(chDex)) {
  443.                     chDex.CaseConversion = caseConversion;
  444.                 }
  445.                
  446.                 // Now let's add the character to the string description table.
  447.                 // For code clarity we treat all characters as localizable symbols (can have multi-char representation).
  448.                
  449.                 if (charType != CharType.Separator) {
  450.                     locSymbol = ch.ToString();
  451.                 }
  452.                
  453.                 foreach (char chVal in locSymbol) {
  454.                     this.testString.Append(chVal);
  455.                     this.stringDescriptor.Add(chDex);
  456.                     testPosition++;
  457.                 }
  458.             }
  459.            
  460.             //
  461.             // Trim test string to needed size.
  462.             //
  463.             this.testString.Capacity = this.testString.Length;
  464.         }
  465.        
  466.        
  467.         ////// Properties
  468.        
  469.        
  470.         /// <devdoc>
  471.         /// Specifies whether the prompt character should be treated as a valid input character or not.
  472.         /// </devdoc>
  473.         public bool AllowPromptAsInput {
  474.             get { return this.flagState[ALLOW_PROMPT_AS_INPUT]; }
  475.         }
  476.        
  477.         /// <devdoc>
  478.         /// Retreives the number of editable characters that have been set.
  479.         /// </devdoc>
  480.         public int AssignedEditPositionCount {
  481.             get { return this.assignedCharCount; }
  482.         }
  483.        
  484.         /// <devdoc>
  485.         /// Retreives the number of editable characters that have been set.
  486.         /// </devdoc>
  487.         public int AvailableEditPositionCount {
  488.             get { return this.EditPositionCount - this.assignedCharCount; }
  489.         }
  490.        
  491.         /// <devdoc>
  492.         /// Creates a 'clean' (no text assigned) MaskedTextProvider instance with the same property values as the
  493.         /// current instance.
  494.         /// Derived classes can override this method and call base.Clone to get proper cloning semantics but must
  495.         /// implement the full-paramter contructor (passing parameters to the base constructor as well).
  496.         /// </devdoc>
  497.         [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2113:SecureLateBindingMethods")]
  498.         public object Clone()
  499.         {
  500.             MaskedTextProvider clonedProvider;
  501.             Type providerType = this.GetType();
  502.            
  503.             if (providerType == maskTextProviderType) {
  504.                 clonedProvider = new MaskedTextProvider(this.Mask, this.Culture, this.AllowPromptAsInput, this.PromptChar, this.PasswordChar, this.AsciiOnly);
  505.             }
  506.             // A derived Type instance used.
  507.             else {
  508.                 object[] parameters = new object[] {this.Mask, this.Culture, this.AllowPromptAsInput, this.PromptChar, this.PasswordChar, this.AsciiOnly};
  509.                
  510.                 clonedProvider = SecurityUtils.SecureCreateInstance(providerType, parameters) as MaskedTextProvider;
  511.             }
  512.            
  513.             clonedProvider.ResetOnPrompt = false;
  514.             clonedProvider.ResetOnSpace = false;
  515.             clonedProvider.SkipLiterals = false;
  516.            
  517.             for (int position = 0; position < this.testString.Length; position++) {
  518.                 CharDescriptor chDex = this.stringDescriptor[position];
  519.                
  520.                 if (IsEditPosition(chDex) && chDex.IsAssigned) {
  521.                     clonedProvider.Replace(this.testString[position], position);
  522.                 }
  523.             }
  524.            
  525.             clonedProvider.ResetOnPrompt = this.ResetOnPrompt;
  526.             clonedProvider.ResetOnSpace = this.ResetOnSpace;
  527.             clonedProvider.SkipLiterals = this.SkipLiterals;
  528.             clonedProvider.IncludeLiterals = this.IncludeLiterals;
  529.             clonedProvider.IncludePrompt = this.IncludePrompt;
  530.            
  531.             return clonedProvider;
  532.         }
  533.        
  534.         /// <devdoc>
  535.         /// The culture that determines the value of the localizable mask language separators and placeholders.
  536.         /// </devdoc>
  537.         public CultureInfo Culture {
  538.             get { return this.culture; }
  539.         }
  540.        
  541.         /// <devdoc>
  542.         /// The system password char.
  543.         /// </devdoc>
  544.         public static char DefaultPasswordChar {
  545. // ComCtl32.dll V6 (WindowsXP) provides a nice black circle but we don't want to attempt to simulate it
  546. // here to avoid hard coding values. MaskedTextBox picks up the right value at run time from comctl32.
  547.             get { return '*'; }
  548.         }
  549.        
  550.         /// <devdoc>
  551.         /// The number of editable positions in the test string.
  552.         /// </devdoc>
  553.         public int EditPositionCount {
  554.             get { return this.optionalEditChars + this.requiredEditChars; }
  555.         }
  556.        
  557.         /// <devdoc>
  558.         /// Returns a new IEnumerator object containing the editable positions in the test string.
  559.         /// </devdoc>
  560.         public System.Collections.IEnumerator EditPositions {
  561.             get {
  562.                 List<int> editPositions = new List<int>();
  563.                 int position = 0;
  564.                
  565.                 foreach (CharDescriptor chDex in this.stringDescriptor) {
  566.                     if (IsEditPosition(chDex)) {
  567.                         editPositions.Add(position);
  568.                     }
  569.                    
  570.                     position++;
  571.                 }
  572.                
  573.                 return ((System.Collections.IList)editPositions).GetEnumerator();
  574.             }
  575.         }
  576.        
  577.         /// <devdoc>
  578.         /// Specifies whether the formatted string should include literals.
  579.         /// </devdoc>
  580.         public bool IncludeLiterals {
  581.             get { return this.flagState[INCLUDE_LITERALS]; }
  582.             set { this.flagState[INCLUDE_LITERALS] = value; }
  583.         }
  584.        
  585.         /// <devdoc>
  586.         /// Specifies whether or not the prompt character should be included in the formatted text when there are
  587.         /// character slots available in the mask.
  588.         /// </devdoc>
  589.         public bool IncludePrompt {
  590.             get { return this.flagState[INCLUDE_PROMPT]; }
  591.             set { this.flagState[INCLUDE_PROMPT] = value; }
  592.         }
  593.        
  594.         /// <devdoc>
  595.         /// Specifies whether only ASCII characters are accepted as valid input.
  596.         /// </devdoc>
  597.         public bool AsciiOnly {
  598.             get { return this.flagState[ASCII_ONLY]; }
  599.         }
  600.        
  601.         /// <devdoc>
  602.         /// Specifies whether the user text is to be rendered as password characters.
  603.         /// </devdoc>
  604.         public bool IsPassword {
  605.             get { return this.passwordChar != '\0'; }
  606.            
  607.             set {
  608.                 if (this.IsPassword != value) {
  609.                     this.passwordChar = value ? DefaultPasswordChar : nullPasswordChar;
  610.                 }
  611.             }
  612.         }
  613.        
  614.         /// <devdoc>
  615.         /// A negative value representing an index outside the test string.
  616.         /// </devdoc>
  617.         public static int InvalidIndex {
  618.             get { return invalidIndex; }
  619.         }
  620.        
  621.         /// <devdoc>
  622.         /// The last edit position (relative to the origin not to time) in the test string where
  623.         /// an input character has been placed. If no position has been assigned, InvalidIndex is returned.
  624.         /// </devdoc>
  625.         public int LastAssignedPosition {
  626.             get { return FindAssignedEditPositionFrom(this.testString.Length - 1, backward); }
  627.         }
  628.        
  629.         /// <devdoc>
  630.         /// Specifies the length of the test string.
  631.         /// </devdoc>
  632.         public int Length {
  633.             get { return this.testString.Length; }
  634.         }
  635.        
  636.         /// <devdoc>
  637.         /// The mask to be applied to the test string.
  638.         /// </devdoc>
  639.         public string Mask {
  640.             get { return this.mask; }
  641.         }
  642.        
  643.         /// <devdoc>
  644.         /// Specifies whether all required inputs have been provided into the mask successfully.
  645.         /// </devdoc>
  646.         public bool MaskCompleted {
  647.             get {
  648.                 Debug.Assert(this.assignedCharCount >= 0, "Invalid count of assigned chars.");
  649.                 return this.requiredCharCount == this.requiredEditChars;
  650.             }
  651.         }
  652.        
  653.         /// <devdoc>
  654.         /// Specifies whether all inputs (required and optional) have been provided into the mask successfully.
  655.         /// </devdoc>
  656.         public bool MaskFull {
  657.             get {
  658.                 Debug.Assert(this.assignedCharCount >= 0, "Invalid count of assigned chars.");
  659.                 return this.assignedCharCount == this.EditPositionCount;
  660.             }
  661.         }
  662.        
  663.         /// <devdoc>
  664.         /// Specifies the character to be used in the formatted string in place of editable characters.
  665.         /// Use the null character '\0' to reset this property.
  666.         /// </devdoc>
  667.         public char PasswordChar {
  668.             get { return this.passwordChar; }
  669.            
  670.             set {
  671.                 if (value == this.promptChar) {
  672.                     // Prompt and password chars must be different.
  673.                     throw new InvalidOperationException(SR.GetString(SR.MaskedTextProviderPasswordAndPromptCharError));
  674.                 }
  675.                
  676.                 if (!IsValidPasswordChar(value) && (value != nullPasswordChar)) {
  677.                     // Same message as in SR.MaskedTextBoxInvalidCharError.
  678.                     throw new ArgumentException(SR.GetString(SR.MaskedTextProviderInvalidCharError));
  679.                 }
  680.                
  681.                 if (value != this.passwordChar) {
  682.                     this.passwordChar = value;
  683.                 }
  684.             }
  685.         }
  686.        
  687.         /// <devdoc>
  688.         /// Specifies the prompt character to be used in the formatted string for unsupplied characters.
  689.         /// </devdoc>
  690.         public char PromptChar {
  691.             get { return this.promptChar; }
  692.            
  693.             set {
  694.                 if (value == this.passwordChar) {
  695.                     // Prompt and password chars must be different.
  696.                     throw new InvalidOperationException(SR.GetString(SR.MaskedTextProviderPasswordAndPromptCharError));
  697.                 }
  698.                
  699.                 if (!IsPrintableChar(value)) {
  700.                     // Same message as in SR.MaskedTextBoxInvalidCharError.
  701.                     throw new ArgumentException(SR.GetString(SR.MaskedTextProviderInvalidCharError));
  702.                 }
  703.                
  704.                 if (value != this.promptChar) {
  705.                     this.promptChar = value;
  706.                    
  707.                     for (int position = 0; position < this.testString.Length; position++) {
  708.                         CharDescriptor chDex = this.stringDescriptor[position];
  709.                        
  710.                         if (IsEditPosition(position) && !chDex.IsAssigned) {
  711.                             this.testString[position] = this.promptChar;
  712.                         }
  713.                     }
  714.                 }
  715.             }
  716.         }
  717.        
  718.        
  719.        
  720.         /// <devdoc>
  721.         /// Specifies whether to reset and skip the current position if editable, when the input character has
  722.         /// the same value as the prompt.
  723.         ///
  724.         /// This is useful when assigning text that was saved including the prompt; in this case
  725.         /// we don't want to take the prompt character as valid input but don't want to fail the test either.
  726.         /// </devdoc>
  727.         public bool ResetOnPrompt {
  728.             get { return this.flagState[RESET_ON_PROMPT]; }
  729.             set { this.flagState[RESET_ON_PROMPT] = value; }
  730.         }
  731.        
  732.         /// <devdoc>
  733.         /// Specifies whether to reset and skip the current position if editable, when the input is the space character.
  734.         ///
  735.         /// This is useful when assigning text that was saved excluding the prompt (prompt replaced with spaces);
  736.         /// in this case we don't want to take the space but instead, reset the postion (or just skip it) so the
  737.         /// next input character gets positioned correctly.
  738.         /// </devdoc>
  739.         public bool ResetOnSpace {
  740.             get { return this.flagState[SKIP_SPACE]; }
  741.             set { this.flagState[SKIP_SPACE] = value; }
  742.         }
  743.        
  744.        
  745.         /// <devdoc>
  746.         /// Specifies whether to skip the current position if non-editable and the input character has the same
  747.         /// value as the literal at that position.
  748.         ///
  749.         /// This is useful for round-tripping the text when saved with literals; when assigned back we don't want
  750.         /// to treat literals as input.
  751.         /// </devdoc>
  752.         public bool SkipLiterals {
  753.             get { return this.flagState[RESET_ON_LITERALS]; }
  754.             set { this.flagState[RESET_ON_LITERALS] = value; }
  755.         }
  756.        
  757.         /// <devdoc>
  758.         /// Indexer.
  759.         /// </devdoc>
  760.         public char this[int index]
  761.         {
  762.             get {
  763.                 if (index < 0 || index >= this.testString.Length) {
  764.                     throw new IndexOutOfRangeException(index.ToString(CultureInfo.CurrentCulture));
  765.                 }
  766.                
  767.                 return this.testString[index];
  768.             }
  769.         }
  770.        
  771.         ////// Methods
  772.        
  773.         /// <devdoc>
  774.         /// Attempts to add the specified charactert to the last unoccupied positions in the test string (append text to
  775.         /// the virtual string).
  776.         /// Returns true on success, false otherwise.
  777.         /// </devdoc>
  778.         public bool Add(char input)
  779.         {
  780.             int dummyVar;
  781.             MaskedTextResultHint dummyVar2;
  782.             return Add(input, out dummyVar, out dummyVar2);
  783.         }
  784.        
  785.         /// <devdoc>
  786.         /// Attempts to add the specified charactert to the last unoccupied positions in the test string (append text to
  787.         /// the virtual string).
  788.         /// On exit the testPosition contains last position where the primary operation was actually performed if successful,
  789.         /// otherwise the first position that made the test fail. This position is relative to the test string.
  790.         /// The MaskedTextResultHint out param gives a hint about the operation result reason.
  791.         /// Returns true on success, false otherwise.
  792.         /// </devdoc>
  793.         public bool Add(char input, out int testPosition, out MaskedTextResultHint resultHint)
  794.         {
  795.             int lastAssignedPos = this.LastAssignedPosition;
  796.            
  797.             // at the last edit char position.
  798.             if (lastAssignedPos == this.testString.Length - 1) {
  799.                 testPosition = this.testString.Length;
  800.                 resultHint = MaskedTextResultHint.UnavailableEditPosition;
  801.                 return false;
  802.             }
  803.            
  804.             // Get position after last assigned position.
  805.             testPosition = lastAssignedPos + 1;
  806.             testPosition = FindEditPositionFrom(testPosition, forward);
  807.            
  808.             if (testPosition == invalidIndex) {
  809.                 resultHint = MaskedTextResultHint.UnavailableEditPosition;
  810.                 testPosition = this.testString.Length;
  811.                 return false;
  812.             }
  813.            
  814.             if (!TestSetChar(input, testPosition, out resultHint)) {
  815.                 return false;
  816.             }
  817.            
  818.             return true;
  819.         }
  820.        
  821.         /// <devdoc>
  822.         /// Attempts to add the characters in the specified string to the last unoccupied positions in the test string
  823.         /// (append text to the virtual string).
  824.         /// Returns true on success, false otherwise.
  825.         /// </devdoc>
  826.         public bool Add(string input)
  827.         {
  828.             int dummyVar;
  829.             MaskedTextResultHint dummyVar2;
  830.             return Add(input, out dummyVar, out dummyVar2);
  831.         }
  832.        
  833.         /// <devdoc>
  834.         /// Attempts to add the characters in the specified string to the last unoccupied positions in the test string
  835.         /// (append text to the virtual string).
  836.         /// On exit the testPosition contains last position where the primary operation was actually performed if successful,
  837.         /// otherwise the first position that made the test fail. This position is relative to the test string.
  838.         /// The MaskedTextResultHint out param gives a hint about the operation result reason.
  839.         /// Returns true on success, false otherwise.
  840.         /// </devdoc>
  841.         public bool Add(string input, out int testPosition, out MaskedTextResultHint resultHint)
  842.         {
  843.             if (input == null) {
  844.                 throw new ArgumentNullException("input");
  845.             }
  846.            
  847.             testPosition = this.LastAssignedPosition + 1;
  848.            
  849.             // nothing to add.
  850.             if (input.Length == 0) {
  851.                 // Get position where the test would be performed.
  852.                 resultHint = MaskedTextResultHint.NoEffect;
  853.                 return true;
  854.             }
  855.            
  856.             return TestSetString(input, testPosition, out testPosition, out resultHint);
  857.         }
  858.        
  859.         /// <devdoc>
  860.         /// Resets the state of the test string edit chars. (Remove all characters from the virtual string).
  861.         /// </devdoc>
  862.         public void Clear()
  863.         {
  864.             MaskedTextResultHint dummyHint;
  865.             Clear(out dummyHint);
  866.         }
  867.        
  868.         /// <devdoc>
  869.         /// Resets the state of the test string edit chars. (Remove all characters from the virtual string).
  870.         /// The MaskedTextResultHint out param gives more information about the operation result.
  871.         /// </devdoc>
  872.         public void Clear(out MaskedTextResultHint resultHint)
  873.         {
  874.             if (this.assignedCharCount == 0) {
  875.                 resultHint = MaskedTextResultHint.NoEffect;
  876.                 return;
  877.             }
  878.            
  879.             resultHint = MaskedTextResultHint.Success;
  880.            
  881.             for (int position = 0; position < this.testString.Length; position++) {
  882.                 ResetChar(position);
  883.             }
  884.         }
  885.        
  886.         /// <devdoc>
  887.         /// Gets the position of the first edit char in the test string, the search starts from the specified
  888.         /// position included.
  889.         /// Returns InvalidIndex if it doesn't find one.
  890.         /// </devdoc>
  891.         public int FindAssignedEditPositionFrom(int position, bool direction)
  892.         {
  893.             if (this.assignedCharCount == 0) {
  894.                 return invalidIndex;
  895.             }
  896.            
  897.             int startPosition;
  898.             int endPosition;
  899.            
  900.             if (direction == forward) {
  901.                 startPosition = position;
  902.                 endPosition = this.testString.Length - 1;
  903.             }
  904.             else {
  905.                 startPosition = 0;
  906.                 endPosition = position;
  907.             }
  908.            
  909.             return FindAssignedEditPositionInRange(startPosition, endPosition, direction);
  910.         }
  911.        
  912.         /// <devdoc>
  913.         /// Gets the position of the first edit char in the test string in the specified range, the search starts from
  914.         /// the specified position included.
  915.         /// Returns InvalidIndex if it doesn't find one.
  916.         /// </devdoc>
  917.         public int FindAssignedEditPositionInRange(int startPosition, int endPosition, bool direction)
  918.         {
  919.             if (this.assignedCharCount == 0) {
  920.                 return invalidIndex;
  921.             }
  922.            
  923.             return FindEditPositionInRange(startPosition, endPosition, direction, editAssigned);
  924.         }
  925.        
  926.         /// <devdoc>
  927.         /// Gets the position of the first assigned edit char in the test string, the search starts from the specified
  928.         /// position included and in the direction specified (true == forward). The positions are relative to the test
  929.         /// string.
  930.         /// Returns InvalidIndex if it doesn't find one.
  931.         /// </devdoc>
  932.         public int FindEditPositionFrom(int position, bool direction)
  933.         {
  934.             int startPosition;
  935.             int endPosition;
  936.            
  937.             if (direction == forward) {
  938.                 startPosition = position;
  939.                 endPosition = this.testString.Length - 1;
  940.             }
  941.             else {
  942.                 startPosition = 0;
  943.                 endPosition = position;
  944.             }
  945.            
  946.             return FindEditPositionInRange(startPosition, endPosition, direction);
  947.         }
  948.        
  949.         /// <devdoc>
  950.         /// Gets the position of the first assigned edit char in the test string; the search is performed in the specified
  951.         /// positions range and in the specified direction.
  952.         /// The positions are relative to the test string.
  953.         /// Returns InvalidIndex if it doesn't find one.
  954.         /// </devdoc>
  955.         public int FindEditPositionInRange(int startPosition, int endPosition, bool direction)
  956.         {
  957.             CharType editCharFlags = CharType.EditOptional | CharType.EditRequired;
  958.             return FindPositionInRange(startPosition, endPosition, direction, editCharFlags);
  959.         }
  960.        
  961.         /// <devdoc>
  962.         /// Gets the position of the first edit char in the test string in the specified range, according to the
  963.         /// assignedRequired parameter; if true, it gets the first assigned position otherwise the first unassigned one.
  964.         /// The search starts from the specified position included.
  965.         /// Returns InvalidIndex if it doesn't find one.
  966.         /// </devdoc>
  967.         private int FindEditPositionInRange(int startPosition, int endPosition, bool direction, byte assignedStatus)
  968.         {
  969.             // out of range position is handled in FindEditPositionFrom method.
  970.             int testPosition;
  971.            
  972.             do {
  973.                 testPosition = FindEditPositionInRange(startPosition, endPosition, direction);
  974.                
  975.                 // didn't find any.
  976.                 if (testPosition == invalidIndex) {
  977.                     break;
  978.                 }
  979.                
  980.                 CharDescriptor chDex = this.stringDescriptor[testPosition];
  981.                
  982.                 switch (assignedStatus) {
  983.                     case editUnassigned:
  984.                         if (!chDex.IsAssigned) {
  985.                             return testPosition;
  986.                         }
  987.                         break;
  988.                     case editAssigned:
  989.                        
  990.                         if (chDex.IsAssigned) {
  991.                             return testPosition;
  992.                         }
  993.                         break;
  994.                     default:
  995.                        
  996.                         // don't care
  997.                         return testPosition;
  998.                 }
  999.                
  1000.                 if (direction == forward) {
  1001.                     startPosition++;
  1002.                 }
  1003.                 else {
  1004.                     endPosition--;
  1005.                 }
  1006.             }
  1007.             while (startPosition <= endPosition);
  1008.            
  1009.             return invalidIndex;
  1010.         }
  1011.        
  1012.         /// <devdoc>
  1013.         /// Gets the position of the first non edit position in the test string; the search is performed from the specified
  1014.         /// position and in the specified direction.
  1015.         /// The positions are relative to the test string.
  1016.         /// Returns InvalidIndex if it doesn't find one.
  1017.         /// </devdoc>
  1018.         public int FindNonEditPositionFrom(int position, bool direction)
  1019.         {
  1020.             int startPosition;
  1021.             int endPosition;
  1022.            
  1023.             if (direction == forward) {
  1024.                 startPosition = position;
  1025.                 endPosition = this.testString.Length - 1;
  1026.             }
  1027.             else {
  1028.                 startPosition = 0;
  1029.                 endPosition = position;
  1030.             }
  1031.            
  1032.             return FindNonEditPositionInRange(startPosition, endPosition, direction);
  1033.         }
  1034.        
  1035.         /// <devdoc>
  1036.         /// Gets the position of the first non edit position in the test string; the search is performed in the specified
  1037.         /// positions range and in the specified direction.
  1038.         /// The positions are relative to the test string.
  1039.         /// Returns InvalidIndex if it doesn't find one.
  1040.         /// </devdoc>
  1041.         public int FindNonEditPositionInRange(int startPosition, int endPosition, bool direction)
  1042.         {
  1043.             CharType literalCharFlags = CharType.Literal | CharType.Separator;
  1044.             return FindPositionInRange(startPosition, endPosition, direction, literalCharFlags);
  1045.         }
  1046.        
  1047.         /// <devdoc>
  1048.         /// Finds a position in the test string according to the needed position type (needEditPos).
  1049.         /// The positions are relative to the test string.
  1050.         /// Returns InvalidIndex if it doesn't find one.
  1051.         /// </devdoc>
  1052.         private int FindPositionInRange(int startPosition, int endPosition, bool direction, CharType charTypeFlags)
  1053.         {
  1054.             if (startPosition < 0) {
  1055.                 startPosition = 0;
  1056.             }
  1057.            
  1058.             if (endPosition >= this.testString.Length) {
  1059.                 endPosition = this.testString.Length - 1;
  1060.             }
  1061.            
  1062.             if (startPosition > endPosition) {
  1063.                 return invalidIndex;
  1064.             }
  1065.            
  1066.             // Iterate through the test string until we find an edit char position.
  1067.             int testPosition;
  1068.            
  1069.             while (startPosition <= endPosition) {
  1070.                 testPosition = (direction == forward) ? startPosition++ : endPosition--;
  1071.                
  1072.                 CharDescriptor chDex = this.stringDescriptor[testPosition];
  1073.                
  1074.                 if ((chDex.CharType & charTypeFlags) == chDex.CharType) {
  1075.                     return testPosition;
  1076.                 }
  1077.             }
  1078.            
  1079.             return invalidIndex;
  1080.         }
  1081.        
  1082.         /// <devdoc>
  1083.         /// Gets the position of the first edit char in the test string, the search starts from the specified
  1084.         /// position included.
  1085.         /// Returns InvalidIndex if it doesn't find one.
  1086.         /// </devdoc>
  1087.         public int FindUnassignedEditPositionFrom(int position, bool direction)
  1088.         {
  1089.             int startPosition;
  1090.             int endPosition;
  1091.            
  1092.             if (direction == forward) {
  1093.                 startPosition = position;
  1094.                 endPosition = this.testString.Length - 1;
  1095.             }
  1096.             else {
  1097.                 startPosition = 0;
  1098.                 endPosition = position;
  1099.             }
  1100.            
  1101.             return FindEditPositionInRange(startPosition, endPosition, direction, editUnassigned);
  1102.         }
  1103.        
  1104.         /// <devdoc>
  1105.         /// Gets the position of the first edit char in the test string in the specified range; the search starts
  1106.         /// from the specified position included.
  1107.         /// Returns InvalidIndex if it doesn't find one.
  1108.         /// </devdoc>
  1109.         [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2233:OperationsShouldNotOverflow")]
  1110.         public int FindUnassignedEditPositionInRange(int startPosition, int endPosition, bool direction)
  1111.         {
  1112.             int position;
  1113.            
  1114.             while (true) {
  1115.                 position = FindEditPositionInRange(startPosition, endPosition, direction, editAny);
  1116.                
  1117.                 if (position == invalidIndex) {
  1118.                     return invalidIndex;
  1119.                 }
  1120.                
  1121.                 CharDescriptor chDex = this.stringDescriptor[position];
  1122.                
  1123.                 if (!chDex.IsAssigned) {
  1124.                     return position;
  1125.                 }
  1126.                
  1127.                 if (direction == forward) {
  1128.                     startPosition++;
  1129.                 }
  1130.                 else {
  1131.                     endPosition--;
  1132.                 }
  1133.             }
  1134.         }
  1135.        
  1136.         /// <devdoc>
  1137.         /// Specifies whether the specified MaskedTextResultHint denotes success or not.
  1138.         /// </devdoc>
  1139.         public static bool GetOperationResultFromHint(MaskedTextResultHint hint)
  1140.         {
  1141.             return ((int)hint) > 0;
  1142.         }
  1143.        
  1144.         /// <devdoc>
  1145.         /// Attempts to insert the specified character at the specified position in the test string.
  1146.         /// (Insert character in the virtual string).
  1147.         /// Returns true on success, false otherwise.
  1148.         /// </devdoc>
  1149.         public bool InsertAt(char input, int position)
  1150.         {
  1151.             if (position < 0 || position >= this.testString.Length) {
  1152.                 return false;
  1153.                 //throw new ArgumentOutOfRangeException("position");
  1154.             }
  1155.            
  1156.             return InsertAt(input.ToString(), position);
  1157.         }
  1158.        
  1159.         /// <devdoc>
  1160.         /// Attempts to insert the specified character at the specified position in the test string, shifting characters
  1161.         /// at upper positions (if any) to make room for the input.
  1162.         /// On exit the testPosition contains last position where the primary operation was actually performed if successful,
  1163.         /// otherwise the first position that made the test fail. This position is relative to the test string.
  1164.         /// The MaskedTextResultHint out param gives more information about the operation result.
  1165.         /// Returns true on success, false otherwise.
  1166.         /// </devdoc>
  1167.         public bool InsertAt(char input, int position, out int testPosition, out MaskedTextResultHint resultHint)
  1168.         {
  1169.             return InsertAt(input.ToString(), position, out testPosition, out resultHint);
  1170.         }
  1171.        
  1172.         /// <devdoc>
  1173.         /// Attempts to insert the characters in the specified string in at the specified position in the test string.
  1174.         /// (Insert characters in the virtual string).
  1175.         /// Returns true on success, false otherwise.
  1176.         /// </devdoc>
  1177.         public bool InsertAt(string input, int position)
  1178.         {
  1179.             int dummyVar;
  1180.             MaskedTextResultHint dummyVar2;
  1181.             return InsertAt(input, position, out dummyVar, out dummyVar2);
  1182.         }
  1183.        
  1184.         /// <devdoc>
  1185.         /// Attempts to insert the characters in the specified string in at the specified position in the test string,
  1186.         /// shifting characters at upper positions (if any) to make room for the input.
  1187.         /// On exit the testPosition contains last position where the primary operation was actually performed if successful,
  1188.         /// otherwise the first position that made the test fail. This position is relative to the test string.
  1189.         /// The MaskedTextResultHint out param gives more information about the operation result.
  1190.         /// Returns true on success, false otherwise.
  1191.         /// </devdoc>
  1192.         public bool InsertAt(string input, int position, out int testPosition, out MaskedTextResultHint resultHint)
  1193.         {
  1194.             if (input == null) {
  1195.                 throw new ArgumentNullException("input");
  1196.             }
  1197.            
  1198.             if (position < 0 || position >= this.testString.Length) {
  1199.                 testPosition = position;
  1200.                 resultHint = MaskedTextResultHint.PositionOutOfRange;
  1201.                 return false;
  1202.                 //throw new ArgumentOutOfRangeException("position");
  1203.             }
  1204.            
  1205.             return InsertAtInt(input, position, out testPosition, out resultHint, false);
  1206.         }
  1207.        
  1208.         /// <devdoc>
  1209.         /// Attempts to insert the characters in the specified string in at the specified position in the test string,
  1210.         /// shifting characters at upper positions (if any) to make room for the input.
  1211.         /// It performs the insertion if the testOnly parameter is false and the test passes.
  1212.         /// On exit the testPosition contains last position where the primary operation was actually performed if successful,
  1213.         /// otherwise the first position that made the test fail. This position is relative to the test string.
  1214.         /// The MaskedTextResultHint out param gives more information about the operation result.
  1215.         /// Returns true on success, false otherwise.
  1216.         /// </devdoc>
  1217.         private bool InsertAtInt(string input, int position, out int testPosition, out MaskedTextResultHint resultHint, bool testOnly)
  1218.         {
  1219.             Debug.Assert(input != null && position >= 0 && position < this.testString.Length, "input param out of range.");
  1220.            
  1221.             // nothing to insert.
  1222.             if (input.Length == 0) {
  1223.                 testPosition = position;
  1224.                 resultHint = MaskedTextResultHint.NoEffect;
  1225.                 return true;
  1226.             }
  1227.            
  1228.             // Test input string first. testPosition will containt the position of the last inserting character from the input.
  1229.             if (!TestString(input, position, out testPosition, out resultHint)) {
  1230.                 return false;
  1231.             }
  1232.            
  1233.             // Now check if we need to open room for the input characters (shift characters right) and if so test the shifting characters.
  1234.            
  1235.             int srcPos = FindEditPositionFrom(position, forward);
  1236.             // source position.
  1237.             bool shiftNeeded = FindAssignedEditPositionInRange(srcPos, testPosition, forward) != invalidIndex;
  1238.             int lastAssignedPos = this.LastAssignedPosition;
  1239.            
  1240.             // no room for shifting.
  1241.             if (shiftNeeded && (testPosition == this.testString.Length - 1)) {
  1242.                 resultHint = MaskedTextResultHint.UnavailableEditPosition;
  1243.                 testPosition = this.testString.Length;
  1244.                 return false;
  1245.             }
  1246.            
  1247.             int dstPos = FindEditPositionFrom(testPosition + 1, forward);
  1248.             // destination position.
  1249.             if (shiftNeeded) {
  1250.                 // Temp hint used not to overwrite the primary operation result hint (from TestString).
  1251.                 MaskedTextResultHint tempHint = MaskedTextResultHint.Unknown;
  1252.                
  1253.                 // Test shifting characters.
  1254.                 while (true) {
  1255.                     if (dstPos == invalidIndex) {
  1256.                         resultHint = MaskedTextResultHint.UnavailableEditPosition;
  1257.                         testPosition = this.testString.Length;
  1258.                         return false;
  1259.                     }
  1260.                    
  1261.                     CharDescriptor chDex = this.stringDescriptor[srcPos];
  1262.                    
  1263.                     // only test assigned positions.
  1264.                     if (chDex.IsAssigned) {
  1265.                         if (!TestChar(this.testString[srcPos], dstPos, out tempHint)) {
  1266.                             resultHint = tempHint;
  1267.                             testPosition = dstPos;
  1268.                             return false;
  1269.                         }
  1270.                     }
  1271.                    
  1272.                     // all shifting positions tested?
  1273.                     if (srcPos == lastAssignedPos) {
  1274.                         break;
  1275.                     }
  1276.                    
  1277.                     srcPos = FindEditPositionFrom(srcPos + 1, forward);
  1278.                     dstPos = FindEditPositionFrom(dstPos + 1, forward);
  1279.                 }
  1280.                
  1281.                 if (tempHint > resultHint) {
  1282.                     resultHint = tempHint;
  1283.                 }
  1284.             }
  1285.            
  1286.             if (testOnly) {
  1287.                 return true;
  1288.                 // test done!
  1289.             }
  1290.            
  1291.             // Tests passed so we can go ahead and shift the existing characters (if needed) and insert the new ones.
  1292.            
  1293.             if (shiftNeeded) {
  1294.                 while (srcPos >= position) {
  1295.                     CharDescriptor chDex = this.stringDescriptor[srcPos];
  1296.                    
  1297.                     if (chDex.IsAssigned) {
  1298.                         SetChar(this.testString[srcPos], dstPos);
  1299.                     }
  1300.                     else {
  1301.                         ResetChar(dstPos);
  1302.                     }
  1303.                    
  1304.                     dstPos = FindEditPositionFrom(dstPos - 1, backward);
  1305.                     srcPos = FindEditPositionFrom(srcPos - 1, backward);
  1306.                 }
  1307.             }
  1308.            
  1309.             // Finally set the input characters.
  1310.             SetString(input, position);
  1311.            
  1312.             return true;
  1313.         }
  1314.        
  1315.         /// <devdoc>
  1316.         /// Helper function for testing char in ascii mode.
  1317.         /// </devdoc>
  1318.         private static bool IsAscii(char c)
  1319.         {
  1320.             //ASCII non-control chars ['!'-'/', '0'-'9', ':'-'@', 'A'-'Z', '['-'''', 'a'-'z', '{'-'~'] all consecutive.
  1321.             return (c >= '!' && c <= '~');
  1322.         }
  1323.        
  1324.         /// <devdoc>
  1325.         /// Helper function for alphanumeric char in ascii mode.
  1326.         /// </devdoc>
  1327.         private static bool IsAciiAlphanumeric(char c)
  1328.         {
  1329.             return (c >= '0' && c <= '9') || (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z');
  1330.         }
  1331.        
  1332.         /// <devdoc>
  1333.         /// Helper function for testing mask language alphanumeric identifiers.
  1334.         /// </devdoc>
  1335.         private static bool IsAlphanumeric(char c)
  1336.         {
  1337.             return char.IsLetter(c) || char.IsDigit(c);
  1338.         }
  1339.        
  1340.         /// <devdoc>
  1341.         /// Helper function for testing letter char in ascii mode.
  1342.         /// </devdoc>
  1343.         private static bool IsAsciiLetter(char c)
  1344.         {
  1345.             return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z');
  1346.         }
  1347.        
  1348.         /// <devdoc>
  1349.         /// Checks wheteher the specified position is available for assignment. Returns false if it is assigned
  1350.         /// or it is not editable, true otherwise.
  1351.         /// </devdoc>
  1352.         public bool IsAvailablePosition(int position)
  1353.         {
  1354.             if (position < 0 || position >= this.testString.Length) {
  1355.                 return false;
  1356.                 //throw new ArgumentOutOfRangeException("position");
  1357.             }
  1358.            
  1359.             CharDescriptor chDex = this.stringDescriptor[position];
  1360.             return IsEditPosition(chDex) && !chDex.IsAssigned;
  1361.         }
  1362.        
  1363.         /// <devdoc>
  1364.         /// Checks wheteher the specified position in the test string is editable.
  1365.         /// </devdoc>
  1366.         public bool IsEditPosition(int position)
  1367.         {
  1368.             if (position < 0 || position >= this.testString.Length) {
  1369.                 return false;
  1370.                 //throw new ArgumentOutOfRangeException("position");
  1371.             }
  1372.            
  1373.             CharDescriptor chDex = this.stringDescriptor[position];
  1374.             return IsEditPosition(chDex);
  1375.         }
  1376.        
  1377.         private static bool IsEditPosition(CharDescriptor charDescriptor)
  1378.         {
  1379.             return (charDescriptor.CharType == CharType.EditRequired || charDescriptor.CharType == CharType.EditOptional);
  1380.         }
  1381.        
  1382.         /// <devdoc>
  1383.         /// Checks wheteher the character in the specified position is a literal and the same as the specified character.
  1384.         /// </devdoc>
  1385.         private static bool IsLiteralPosition(CharDescriptor charDescriptor)
  1386.         {
  1387.             return (charDescriptor.CharType == CharType.Literal) || (charDescriptor.CharType == CharType.Separator);
  1388.         }
  1389.        
  1390.         /// <devdoc>
  1391.         /// Checks wheteher the specified character is valid as part of a mask or an input string.
  1392.         /// </devdoc>
  1393.         private static bool IsPrintableChar(char c)
  1394.         {
  1395.             return char.IsLetterOrDigit(c) || char.IsPunctuation(c) || char.IsSymbol(c) || (c == spaceChar);
  1396.         }
  1397.        
  1398.         /// <devdoc>
  1399.         /// Checks wheteher the specified character is a valid input char.
  1400.         /// </devdoc>
  1401.         public static bool IsValidInputChar(char c)
  1402.         {
  1403.             return IsPrintableChar(c);
  1404.         }
  1405.        
  1406.         /// <devdoc>
  1407.         /// Checks wheteher the specified character is a valid input char.
  1408.         /// </devdoc>
  1409.         public static bool IsValidMaskChar(char c)
  1410.         {
  1411.             return IsPrintableChar(c);
  1412.         }
  1413.        
  1414.         /// <devdoc>
  1415.         /// Checks wheteher the specified character is a valid password char.
  1416.         /// </devdoc>
  1417.         public static bool IsValidPasswordChar(char c)
  1418.         {
  1419.             return IsPrintableChar(c) || (c == '\0');
  1420.             // null character means password reset.
  1421.         }
  1422.        
  1423.         /// <devdoc>
  1424.         /// Removes the last character from the formatted string. (Remove last character in virtual string).
  1425.         /// </devdoc>
  1426.         public bool Remove()
  1427.         {
  1428.             int dummyVar;
  1429.             MaskedTextResultHint dummyVar2;
  1430.             return Remove(out dummyVar, out dummyVar2);
  1431.         }
  1432.        
  1433.         /// <devdoc>
  1434.         /// Removes the last character from the formatted string. (Remove last character in virtual string).
  1435.         /// On exit the out param contains the position where the operation was actually performed.
  1436.         /// This position is relative to the test string.
  1437.         /// The MaskedTextResultHint out param gives more information about the operation result.
  1438.         /// Returns true on success, false otherwise.
  1439.         /// </devdoc>
  1440.         public bool Remove(out int testPosition, out MaskedTextResultHint resultHint)
  1441.         {
  1442.             int lastAssignedPos = this.LastAssignedPosition;
  1443.            
  1444.             if (lastAssignedPos == invalidIndex) {
  1445.                 testPosition = 0;
  1446.                 resultHint = MaskedTextResultHint.NoEffect;
  1447.                 return true;
  1448.                 // nothing to remove.
  1449.             }
  1450.            
  1451.             ResetChar(lastAssignedPos);
  1452.            
  1453.             testPosition = lastAssignedPos;
  1454.             resultHint = MaskedTextResultHint.Success;
  1455.            
  1456.             return true;
  1457.         }
  1458.        
  1459.         /// <devdoc>
  1460.         /// Removes the character from the formatted string at the specified position and shifts characters
  1461.         /// left.
  1462.         /// True if character shifting is successful.
  1463.         /// </devdoc>
  1464.         public bool RemoveAt(int position)
  1465.         {
  1466.             return RemoveAt(position, position);
  1467.         }
  1468.        
  1469.         /// <devdoc>
  1470.         /// Removes all characters in edit position from in the test string at the specified start and end positions
  1471.         /// and shifts any remaining characters left. (Remove characters from the virtual string).
  1472.         /// Returns true on success, false otherwise.
  1473.         /// </devdoc>
  1474.         public bool RemoveAt(int startPosition, int endPosition)
  1475.         {
  1476.             int dummyVar;
  1477.             MaskedTextResultHint dummyVar2;
  1478.             return RemoveAt(startPosition, endPosition, out dummyVar, out dummyVar2);
  1479.         }
  1480.        
  1481.         /// <devdoc>
  1482.         /// Removes all characters in edit position from in the test string at the specified start and end positions
  1483.         /// and shifts any remaining characters left.
  1484.         /// On exit the testPosition contains last position where the primary operation was actually performed if successful,
  1485.         /// otherwise the first position that made the test fail. This position is relative to the test string.
  1486.         /// The MaskedTextResultHint out param gives more information about the operation result.
  1487.         /// Returns true on success, false otherwise.
  1488.         /// </devdoc>
  1489.         public bool RemoveAt(int startPosition, int endPosition, out int testPosition, out MaskedTextResultHint resultHint)
  1490.         {
  1491.             if (endPosition >= this.testString.Length) {
  1492.                 testPosition = endPosition;
  1493.                 resultHint = MaskedTextResultHint.PositionOutOfRange;
  1494.                 return false;
  1495.                 //throw new ArgumentOutOfRangeException("endPosition");
  1496.             }
  1497.            
  1498.             if (startPosition < 0 || startPosition > endPosition) {
  1499.                 testPosition = startPosition;
  1500.                 resultHint = MaskedTextResultHint.PositionOutOfRange;
  1501.                 return false;
  1502.                 //throw new ArgumentOutOfRangeException("startPosition");
  1503.             }
  1504.            
  1505.                 /*testOnly*/            return RemoveAtInt(startPosition, endPosition, out testPosition, out resultHint, false);
  1506.         }
  1507.        
  1508.         /// <devdoc>
  1509.         /// Removes all characters in edit position from in the test string at the specified start and end positions
  1510.         /// and shifts any remaining characters left.
  1511.         /// If testOnly parameter is set to false and the test passes it performs the operations on the characters.
  1512.         /// On exit the testPosition contains last position where the primary operation was actually performed if successful,
  1513.         /// otherwise the first position that made the test fail. This position is relative to the test string.
  1514.         /// The MaskedTextResultHint out param gives more information about the operation result.
  1515.         /// Returns true on success, false otherwise.
  1516.         /// </devdoc>
  1517.         private bool RemoveAtInt(int startPosition, int endPosition, out int testPosition, out MaskedTextResultHint resultHint, bool testOnly)
  1518.         {
  1519.             Debug.Assert(startPosition >= 0 && startPosition <= endPosition && endPosition < this.testString.Length, "Out of range input value.");
  1520.            
  1521.             // Check if we need to shift characters left to occupied the positions left by the characters being removed.
  1522.             int lastAssignedPos = this.LastAssignedPosition;
  1523.             int dstPos = FindEditPositionInRange(startPosition, endPosition, forward);
  1524.             // first edit position in range.
  1525.             resultHint = MaskedTextResultHint.NoEffect;
  1526.            
  1527.             // nothing to remove.
  1528.             if (dstPos == invalidIndex || dstPos > lastAssignedPos) {
  1529.                 testPosition = startPosition;
  1530.                 return true;
  1531.             }
  1532.            
  1533.             testPosition = startPosition;
  1534.             // On remove range, testPosition remains the same as startPosition.
  1535.             bool shiftNeeded = endPosition < lastAssignedPos;
  1536.             // last assigned position is upper.
  1537.             // if there are assigned characters to be removed (could be that the range doesn't have one, in such case we may be just
  1538.             // be shifting chars), the result hint is success, let's check.
  1539.             if (FindAssignedEditPositionInRange(startPosition, endPosition, forward) != invalidIndex) {
  1540.                 resultHint = MaskedTextResultHint.Success;
  1541.             }
  1542.            
  1543.             if (shiftNeeded) {
  1544.                 // Test shifting characters.
  1545.                
  1546.                 int srcPos = FindEditPositionFrom(endPosition + 1, forward);
  1547.                 // first position to shift left.
  1548.                 int shiftStart = srcPos;
  1549.                 // cache it here so we don't have to search for it later if needed.
  1550.                 MaskedTextResultHint testHint;
  1551.                
  1552.                 startPosition = dstPos;
  1553.                 // actual start position.
  1554.                 while (true) {
  1555.                     char srcCh = this.testString[srcPos];
  1556.                     CharDescriptor chDex = this.stringDescriptor[srcPos];
  1557.                    
  1558.                     // if the shifting character is the prompt and it is at an unassigned position we don't need to test it.
  1559.                     if (srcCh != this.PromptChar || chDex.IsAssigned) {
  1560.                         if (!TestChar(srcCh, dstPos, out testHint)) {
  1561.                             resultHint = testHint;
  1562.                             testPosition = dstPos;
  1563.                             // failed position.
  1564.                             return false;
  1565.                         }
  1566.                     }
  1567.                    
  1568.                     if (srcPos == lastAssignedPos) {
  1569.                         break;
  1570.                     }
  1571.                    
  1572.                     srcPos = FindEditPositionFrom(srcPos + 1, forward);
  1573.                     dstPos = FindEditPositionFrom(dstPos + 1, forward);
  1574.                 }
  1575.                
  1576.                 // shifting characters is a resultHint == sideEffect, update hint if no characters removed (which would be hint == success).
  1577.                 if (MaskedTextResultHint.SideEffect > resultHint) {
  1578.                     resultHint = MaskedTextResultHint.SideEffect;
  1579.                 }
  1580.                
  1581.                 if (testOnly) {
  1582.                     return true;
  1583.                     // test completed.
  1584.                 }
  1585.                
  1586.                 // test passed so shift characters.
  1587.                 srcPos = shiftStart;
  1588.                 dstPos = startPosition;
  1589.                
  1590.                 while (true) {
  1591.                     char srcCh = this.testString[srcPos];
  1592.                     CharDescriptor chDex = this.stringDescriptor[srcPos];
  1593.                    
  1594.                     // if the shifting character is the prompt and it is at an unassigned position we just reset the destination position.
  1595.                     if (srcCh == this.PromptChar && !chDex.IsAssigned) {
  1596.                         ResetChar(dstPos);
  1597.                     }
  1598.                     else {
  1599.                         SetChar(srcCh, dstPos);
  1600.                         ResetChar(srcPos);
  1601.                     }
  1602.                    
  1603.                     if (srcPos == lastAssignedPos) {
  1604.                         break;
  1605.                     }
  1606.                    
  1607.                     srcPos = FindEditPositionFrom(srcPos + 1, forward);
  1608.                     dstPos = FindEditPositionFrom(dstPos + 1, forward);
  1609.                 }
  1610.                
  1611.                 // If shifting character are less than characters to remove in the range, we need to remove the remaining ones in the range;
  1612.                 // update startPosition and ResetString belwo will take care of that.
  1613.                 startPosition = dstPos + 1;
  1614.             }
  1615.            
  1616.             if (startPosition <= endPosition) {
  1617.                 ResetString(startPosition, endPosition);
  1618.             }
  1619.            
  1620.             return true;
  1621.         }
  1622.        
  1623.         /// <devdoc>
  1624.         /// Replaces the first editable character in the test string from the specified position, with the specified
  1625.         /// character (Replace is performed in the virtual string), unless the character at the specified position
  1626.         /// is to be escaped.
  1627.         /// Returns true on success, false otherwise.
  1628.         /// </devdoc>
  1629.         public bool Replace(char input, int position)
  1630.         {
  1631.             int dummyVar;
  1632.             MaskedTextResultHint dummyVar2;
  1633.             return Replace(input, position, out dummyVar, out dummyVar2);
  1634.         }
  1635.        
  1636.         /// <devdoc>
  1637.         /// Replaces the first editable character in the test string from the specified position, with the specified
  1638.         /// character, unless the character at the specified position is to be escaped.
  1639.         /// On exit the testPosition contains last position where the primary operation was actually performed if successful,
  1640.         /// otherwise the first position that made the test fail. This position is relative to the test string.
  1641.         /// The MaskedTextResultHint out param gives more information about the operation result.
  1642.         /// Returns true on success, false otherwise.
  1643.         /// </devdoc>
  1644.         public bool Replace(char input, int position, out int testPosition, out MaskedTextResultHint resultHint)
  1645.         {
  1646.             if (position < 0 || position >= this.testString.Length) {
  1647.                 testPosition = position;
  1648.                 resultHint = MaskedTextResultHint.PositionOutOfRange;
  1649.                 return false;
  1650.                 //throw new ArgumentOutOfRangeException("position");
  1651.             }
  1652.            
  1653.             testPosition = position;
  1654.            
  1655.             // If character is not to be escaped, we need to find the first edit position to test it in.
  1656.             if (!TestEscapeChar(input, testPosition)) {
  1657.                 testPosition = FindEditPositionFrom(testPosition, forward);
  1658.             }
  1659.            
  1660.             if (testPosition == invalidIndex) {
  1661.                 resultHint = MaskedTextResultHint.UnavailableEditPosition;
  1662.                 testPosition = position;
  1663.                 return false;
  1664.             }
  1665.            
  1666.             if (!TestSetChar(input, testPosition, out resultHint)) {
  1667.                 return false;
  1668.             }
  1669.            
  1670.             return true;
  1671.         }
  1672.        
  1673.        
  1674.         /// <devdoc>
  1675.         /// Replaces the first editable character in the test string from the specified position, with the specified
  1676.         /// character and removes any remaining characters in the range unless the character at the specified position
  1677.         /// is to be escaped.
  1678.         /// If specified range covers more than one assigned edit character, shift-left is performed after replacing
  1679.         /// the first character. This is useful when in a edit box the user selects text and types a character to replace it.
  1680.         /// On exit the testPosition contains last position where the primary operation was actually performed if successful,
  1681.         /// otherwise the first position that made the test fail. This position is relative to the test string.
  1682.         /// The MaskedTextResultHint out param gives more information about the operation result.
  1683.         /// Returns true on success, false otherwise.
  1684.         /// </devdoc>
  1685.         public bool Replace(char input, int startPosition, int endPosition, out int testPosition, out MaskedTextResultHint resultHint)
  1686.         {
  1687.             if (endPosition >= this.testString.Length) {
  1688.                 testPosition = endPosition;
  1689.                 resultHint = MaskedTextResultHint.PositionOutOfRange;
  1690.                 return false;
  1691.                 //throw new ArgumentOutOfRangeException("endPosition");
  1692.             }
  1693.            
  1694.             if (startPosition < 0 || startPosition > endPosition) {
  1695.                 testPosition = startPosition;
  1696.                 resultHint = MaskedTextResultHint.PositionOutOfRange;
  1697.                 return false;
  1698.                 //throw new ArgumentOutOfRangeException("startPosition");
  1699.             }
  1700.            
  1701.             if (startPosition == endPosition) {
  1702.                 testPosition = startPosition;
  1703.                 return TestSetChar(input, startPosition, out resultHint);
  1704.             }
  1705.            
  1706.             return Replace(input.ToString(), startPosition, endPosition, out testPosition, out resultHint);
  1707.         }
  1708.        
  1709.         /// <devdoc>
  1710.         /// Replaces the character at the first edit position from the one specified with the first character in the input;
  1711.         /// the rest of the characters in the input will be placed in the test string according to the InsertMode (insert/replace).
  1712.         /// (Replace is performed in the virtual text).
  1713.         /// Returns true on success, false otherwise.
  1714.         /// </devdoc>
  1715.         public bool Replace(string input, int position)
  1716.         {
  1717.             int dummyVar;
  1718.             MaskedTextResultHint dummyVar2;
  1719.             return Replace(input, position, out dummyVar, out dummyVar2);
  1720.         }
  1721.        
  1722.         /// <devdoc>
  1723.         /// Replaces the character at the first edit position from the one specified with the first character in the input;
  1724.         /// the rest of the characters in the input will be placed in the test string according to the InsertMode (insert/replace),
  1725.         /// shifting characters at upper positions (if any) to make room for the entire input.
  1726.         /// On exit the testPosition contains last position where the primary operation was actually performed if successful,
  1727.         /// otherwise the first position that made the test fail. This position is relative to the test string.
  1728.         /// The MaskedTextResultHint out param gives more information about the operation result.
  1729.         /// Returns true on success, false otherwise.
  1730.         /// </devdoc>
  1731.         public bool Replace(string input, int position, out int testPosition, out MaskedTextResultHint resultHint)
  1732.         {
  1733.             if (input == null) {
  1734.                 throw new ArgumentNullException("input");
  1735.             }
  1736.            
  1737.             if (position < 0 || position >= this.testString.Length) {
  1738.                 testPosition = position;
  1739.                 resultHint = MaskedTextResultHint.PositionOutOfRange;
  1740.                 return false;
  1741.                 //throw new ArgumentOutOfRangeException("position");
  1742.             }
  1743.            
  1744.             // remove the character at position.
  1745.             if (input.Length == 0) {
  1746.                 return RemoveAt(position, position, out testPosition, out resultHint);
  1747.             }
  1748.            
  1749.             // At this point, we are replacing characters with the ones in the input.
  1750.            
  1751.             if (!TestSetString(input, position, out testPosition, out resultHint)) {
  1752.                 return false;
  1753.             }
  1754.            
  1755.             return true;
  1756.         }
  1757.        
  1758.         /// <devdoc>
  1759.         /// Replaces the characters in the specified range with the characters in the input string and shifts
  1760.         /// characters appropriately (removing or inserting characters according to whether the input string is
  1761.         /// shorter or larger than the specified range.
  1762.         /// On exit the testPosition contains last position where the primary operation was actually performed if successful,
  1763.         /// otherwise the first position that made the test fail. This position is relative to the test string.
  1764.         /// The MaskedTextResultHint out param gives more information about the operation result.
  1765.         /// Returns true on success, false otherwise.
  1766.         /// </devdoc>
  1767.         public bool Replace(string input, int startPosition, int endPosition, out int testPosition, out MaskedTextResultHint resultHint)
  1768.         {
  1769.             if (input == null) {
  1770.                 throw new ArgumentNullException("input");
  1771.             }
  1772.            
  1773.             if (endPosition >= this.testString.Length) {
  1774.                 testPosition = endPosition;
  1775.                 resultHint = MaskedTextResultHint.PositionOutOfRange;
  1776.                 return false;
  1777.                 //throw new ArgumentOutOfRangeException("endPosition");
  1778.             }
  1779.            
  1780.             if (startPosition < 0 || startPosition > endPosition) {
  1781.                 testPosition = startPosition;
  1782.                 resultHint = MaskedTextResultHint.PositionOutOfRange;
  1783.                 return false;
  1784.                 //throw new ArgumentOutOfRangeException("startPosition");
  1785.             }
  1786.            
  1787.             // remove character at position.
  1788.             if (input.Length == 0) {
  1789.                 return RemoveAt(startPosition, endPosition, out testPosition, out resultHint);
  1790.             }
  1791.            
  1792.             // If replacing the entire text with a same-lenght text, we are just setting (not replacing) the test string to the new value;
  1793.             // in this case we just call SetString.
  1794.             // If the text length is different than the specified range we would need to remove or insert characters; there are three possible
  1795.             // cases as follows:
  1796.             // 1. The text length is the same as edit positions in the range (or no assigned chars): just replace the text, no additional operations needed.
  1797.             // 2. The text is shorter: replace the text in the text string and remove (range - text.Length) characters.
  1798.             // 3. The text is larger: replace range count characters and insert (range - text.Length) characters.
  1799.            
  1800.             // Test input string first and get the last test position to determine what to do.
  1801.             if (!TestString(input, startPosition, out testPosition, out resultHint)) {
  1802.                 return false;
  1803.             }
  1804.            
  1805.             if (this.assignedCharCount > 0) {
  1806.                 // cache out params to preserve the ones from the primary operation (in case of success).
  1807.                 int tempPos;
  1808.                 MaskedTextResultHint tempHint;
  1809.                
  1810.                 // Case 2. Replace + Remove.
  1811.                 if (testPosition < endPosition) {
  1812.                     // Test removing remaining characters.
  1813.                     if (!RemoveAtInt(testPosition + 1, endPosition, out tempPos, out tempHint, false))/*testOnly*/ {
  1814.                         testPosition = tempPos;
  1815.                         resultHint = tempHint;
  1816.                         return false;
  1817.                     }
  1818.                    
  1819.                     // If current result hint is not success (no effect), and character shifting is actually performed, hint = side effect.
  1820.                     if (tempHint == MaskedTextResultHint.Success && resultHint != tempHint) {
  1821.                         resultHint = MaskedTextResultHint.SideEffect;
  1822.                     }
  1823.                 }
  1824.                 // Case 3. Replace + Insert.
  1825.                 else if (testPosition > endPosition) {
  1826.                     // Test shifting existing characters to make room for inserting part of the input.
  1827.                     int lastAssignedPos = this.LastAssignedPosition;
  1828.                     int dstPos = testPosition + 1;
  1829.                     int srcPos = endPosition + 1;
  1830.                    
  1831.                     while (true) {
  1832.                         srcPos = FindEditPositionFrom(srcPos, forward);
  1833.                         dstPos = FindEditPositionFrom(dstPos, forward);
  1834.                        
  1835.                         if (dstPos == invalidIndex) {
  1836.                             testPosition = this.testString.Length;
  1837.                             resultHint = MaskedTextResultHint.UnavailableEditPosition;
  1838.                             return false;
  1839.                         }
  1840.                        
  1841.                         if (!TestChar(this.testString[srcPos], dstPos, out tempHint)) {
  1842.                             testPosition = dstPos;
  1843.                             resultHint = tempHint;
  1844.                             return false;
  1845.                         }
  1846.                        
  1847.                         // If current result hint is not success (no effect), and character shifting is actually performed, hint = success effect.
  1848.                         if (tempHint == MaskedTextResultHint.Success && resultHint != tempHint) {
  1849.                             resultHint = MaskedTextResultHint.Success;
  1850.                         }
  1851.                        
  1852.                         if (srcPos == lastAssignedPos) {
  1853.                             break;
  1854.                         }
  1855.                        
  1856.                         srcPos++;
  1857.                         dstPos++;
  1858.                     }
  1859.                    
  1860.                     // shift test passed, now do it.
  1861.                    
  1862.                     while (dstPos > testPosition) {
  1863.                         SetChar(this.testString[srcPos], dstPos);
  1864.                        
  1865.                         srcPos = FindEditPositionFrom(srcPos - 1, backward);
  1866.                         dstPos = FindEditPositionFrom(dstPos - 1, backward);
  1867.                     }
  1868.                 }
  1869.                 // else endPosition == testPosition, this means replacing the entire text which is the same as Set().
  1870.             }
  1871.            
  1872.             // in all cases we need to replace the input.
  1873.             SetString(input, startPosition);
  1874.             return true;
  1875.         }
  1876.        
  1877.         /// <devdoc>
  1878.         /// Resets the test string character at the specified position.
  1879.         /// </devdoc>
  1880.         private void ResetChar(int testPosition)
  1881.         {
  1882.             CharDescriptor chDex = this.stringDescriptor[testPosition];
  1883.            
  1884.             if (IsEditPosition(testPosition) && chDex.IsAssigned) {
  1885.                 chDex.IsAssigned = false;
  1886.                 this.testString[testPosition] = this.promptChar;
  1887.                 this.assignedCharCount--;
  1888.                
  1889.                 if (chDex.CharType == CharType.EditRequired) {
  1890.                     this.requiredCharCount--;
  1891.                 }
  1892.                
  1893.                 Debug.Assert(this.assignedCharCount >= 0, "Invalid count of assigned chars.");
  1894.             }
  1895.         }
  1896.        
  1897.         /// <devdoc>
  1898.         /// Resets characters in the test string in the range defined by the specified positions.
  1899.         /// Position is relative to the test string and count is the number of edit characters to reset.
  1900.         /// </devdoc>
  1901.         private void ResetString(int startPosition, int endPosition)
  1902.         {
  1903.             Debug.Assert(startPosition >= 0 && endPosition >= 0 && endPosition >= startPosition && endPosition < this.testString.Length, "position out of range.");
  1904.            
  1905.             startPosition = FindAssignedEditPositionFrom(startPosition, forward);
  1906.            
  1907.             if (startPosition != invalidIndex) {
  1908.                 endPosition = FindAssignedEditPositionFrom(endPosition, backward);
  1909.                
  1910.                 while (startPosition <= endPosition) {
  1911.                     startPosition = FindAssignedEditPositionFrom(startPosition, forward);
  1912.                     ResetChar(startPosition);
  1913.                     startPosition++;
  1914.                 }
  1915.             }
  1916.         }
  1917.        
  1918.         /// <devdoc>
  1919.         /// Sets the edit characters in the test string to the ones specified in the input string if all characters
  1920.         /// are valid.
  1921.         /// If passwordChar is assigned, it is rendered in the output string instead of the user-supplied values.
  1922.         /// </devdoc>
  1923.         public bool Set(string input)
  1924.         {
  1925.             int dummyVar;
  1926.             MaskedTextResultHint dummyVar2;
  1927.            
  1928.             return Set(input, out dummyVar, out dummyVar2);
  1929.         }
  1930.        
  1931.         /// <devdoc>
  1932.         /// Sets the edit characters in the test string to the ones specified in the input string if all characters
  1933.         /// are valid.
  1934.         /// On exit the testPosition contains last position where the primary operation was actually performed if successful,
  1935.         /// otherwise the first position that made the test fail. This position is relative to the test string.
  1936.         /// The MaskedTextResultHint out param gives more information about the operation result.
  1937.         /// If passwordChar is assigned, it is rendered in the output string instead of the user-supplied values.
  1938.         /// </devdoc>
  1939.         public bool Set(string input, out int testPosition, out MaskedTextResultHint resultHint)
  1940.         {
  1941.             if (input == null) {
  1942.                 throw new ArgumentNullException("input");
  1943.             }
  1944.            
  1945.             resultHint = MaskedTextResultHint.Unknown;
  1946.             testPosition = 0;
  1947.            
  1948.             // Clearing the input text.
  1949.             if (input.Length == 0) {
  1950.                 Clear(out resultHint);
  1951.                 return true;
  1952.             }
  1953.            
  1954.             if (!TestSetString(input, testPosition, out testPosition, out resultHint)) {
  1955.                 return false;
  1956.             }
  1957.            
  1958.             // Reset remaining characters (if any).
  1959.             int resetPos = FindAssignedEditPositionFrom(testPosition + 1, forward);
  1960.            
  1961.             if (resetPos != invalidIndex) {
  1962.                 ResetString(resetPos, this.testString.Length - 1);
  1963.             }
  1964.            
  1965.             return true;
  1966.         }
  1967.        
  1968.         /// <devdoc>
  1969.         /// Sets the character at the specified position in the test string to the specified value.
  1970.         /// Returns true on success, false otherwise.
  1971.         /// </devdoc>
  1972.         private void SetChar(char input, int position)
  1973.         {
  1974.             Debug.Assert(position >= 0 && position < this.testString.Length, "Position out of range.");
  1975.            
  1976.             CharDescriptor chDex = this.stringDescriptor[position];
  1977.             SetChar(input, position, chDex);
  1978.         }
  1979.        
  1980.         /// <devdoc>
  1981.         /// Sets the character at the specified position in the test string to the specified value.
  1982.         /// SetChar increments the number of assigned characters in the test string.
  1983.         /// </devdoc>
  1984.         private void SetChar(char input, int position, CharDescriptor charDescriptor)
  1985.         {
  1986.             Debug.Assert(position >= 0 && position < this.testString.Length, "Position out of range.");
  1987.             Debug.Assert(charDescriptor != null, "Null character descriptor.");
  1988.            
  1989.             // Get the character info from the char descriptor table.
  1990.             CharDescriptor charDex = this.stringDescriptor[position];
  1991.            
  1992.             // If input is space or prompt and is to be escaped, we are actually resetting the position if assigned,
  1993.             // this doesn't affect literal positions.
  1994.             if (TestEscapeChar(input, position, charDescriptor)) {
  1995.                 ResetChar(position);
  1996.                 return;
  1997.             }
  1998.            
  1999.             Debug.Assert(!IsLiteralPosition(charDex), "Setting char in literal position.");
  2000.            
  2001.             if (Char.IsLetter(input)) {
  2002.                 if (Char.IsUpper(input)) {
  2003.                     if (charDescriptor.CaseConversion == CaseConversion.ToLower) {
  2004.                         input = this.culture.TextInfo.ToLower(input);
  2005.                     }
  2006.                 }
  2007.                 // Char.IsLower( input )
  2008.                 else {
  2009.                     if (charDescriptor.CaseConversion == CaseConversion.ToUpper) {
  2010.                         input = this.culture.TextInfo.ToUpper(input);
  2011.                     }
  2012.                 }
  2013.             }
  2014.            
  2015.             this.testString[position] = input;
  2016.            
  2017.             // if position not counted for already (replace case) we do it (add case).
  2018.             if (!charDescriptor.IsAssigned) {
  2019.                 charDescriptor.IsAssigned = true;
  2020.                 this.assignedCharCount++;
  2021.                
  2022.                 if (charDescriptor.CharType == CharType.EditRequired) {
  2023.                     this.requiredCharCount++;
  2024.                 }
  2025.             }
  2026.            
  2027.             Debug.Assert(this.assignedCharCount <= this.EditPositionCount, "Invalid count of assigned chars.");
  2028.         }
  2029.        
  2030.         /// <devdoc>
  2031.         /// Sets the characters in the test string starting from the specified position, to the ones in the input
  2032.         /// string. It assumes there's enough edit positions to hold the characters in the input string (so call
  2033.         /// TestString before calling SetString).
  2034.         /// The position is relative to the test string.
  2035.         /// </devdoc>
  2036.         private void SetString(string input, int testPosition)
  2037.         {
  2038.             foreach (char ch in input) {
  2039.                 // If character is not to be escaped, we need to find the first edit position to test it in.
  2040.                 if (!TestEscapeChar(ch, testPosition)) {
  2041.                     testPosition = FindEditPositionFrom(testPosition, forward);
  2042.                 }
  2043.                
  2044.                 SetChar(ch, testPosition);
  2045.                 testPosition++;
  2046.             }
  2047.         }
  2048.        
  2049.         #if OUT
  2050.        
  2051.         private void SynchronizeInputOptions(MaskedTextProvider mtp, bool includePrompt, bool includeLiterals)
  2052.         {
  2053.             // Input options are processed in the following order:
  2054.             // 1. Literals
  2055.             // 2. Prompts
  2056.             // 3. Spaces.
  2057.            
  2058.             // If literals not included in the output, it should not attempt to skip literals.
  2059.             mtp.SkipLiterals = includeLiterals;
  2060.            
  2061.             // MaskedTextProvider processes space as follows:
  2062.             // If it is an input character, it would be processed as such (no scaping).
  2063.             // If it is a literal, it would be processed first since literals are processed first.
  2064.             // If it is the same as the prompt, the value of IncludePrompt does not matter because the output
  2065.             // will be the same; this case should be treated as if IncludePrompt was true. Observe that
  2066.             // AllowPromptAsInput would not be affected because ResetOnPrompt has higher precedence.
  2067.             if (mtp.PromptChar == ' ') {
  2068.                 includePrompt = true;
  2069.             }
  2070.            
  2071.             // If prompts are not present in the output, spaces will replace the prompts and will be process
  2072.             // by ResetOnSpace. Literals characters same as the prompt will be processed as literals first.
  2073.             // If prompts present positions will be rest.
  2074.             // Exception: PromptChar == space.
  2075.             mtp.ResetOnPrompt = includePrompt;
  2076.            
  2077.             // If no prompts in the output, the input may contain spaces replacing the prompt, reset on space
  2078.             // should be enabled. If prompts included in the output, spaces will be processed as literals.
  2079.             // Exception: PromptChar == space.
  2080.             mtp.ResetOnSpace = !includePrompt;
  2081.         }
  2082.         #endif
  2083.        
  2084.         /// <devdoc>
  2085.         /// Tests whether the character at the specified position in the test string can be set to the specified
  2086.         /// value.
  2087.         /// The position specified is relative to the test string.
  2088.         /// The MaskedTextResultHint out param gives more information about the operation result.
  2089.         /// Returns true on success, false otherwise.
  2090.         /// </devdoc>
  2091.         private bool TestChar(char input, int position, out MaskedTextResultHint resultHint)
  2092.         {
  2093.             // boundary checks are performed in the public methods.
  2094.             Debug.Assert(position >= 0 && position < this.testString.Length, "Position out of range.");
  2095.            
  2096.             if (!IsPrintableChar(input)) {
  2097.                 resultHint = MaskedTextResultHint.InvalidInput;
  2098.                 return false;
  2099.             }
  2100.            
  2101.             // Get the character info from the char descriptor table.
  2102.             CharDescriptor charDex = this.stringDescriptor[position];
  2103.            
  2104.            
  2105.             if (IsLiteralPosition(charDex)) {
  2106.                 if (this.SkipLiterals && (input == this.testString[position])) {
  2107.                     resultHint = MaskedTextResultHint.CharacterEscaped;
  2108.                     return true;
  2109.                 }
  2110.                
  2111.                 resultHint = MaskedTextResultHint.NonEditPosition;
  2112.                 return false;
  2113.             }
  2114.            
  2115.             if (input == this.promptChar) {
  2116.                 if (this.ResetOnPrompt) {
  2117.                     // Position would be reset.
  2118.                     if (IsEditPosition(charDex) && charDex.IsAssigned) {
  2119.                         resultHint = MaskedTextResultHint.SideEffect;
  2120.                     }
  2121.                     else {
  2122.                         resultHint = MaskedTextResultHint.CharacterEscaped;
  2123.                     }
  2124.                     return true;
  2125.                     // test does not fail for prompt when it is to be scaped.
  2126.                 }
  2127.                
  2128.                 // Escaping precedes AllowPromptAsInput. Now test for it.
  2129.                 if (!this.AllowPromptAsInput) {
  2130.                     resultHint = MaskedTextResultHint.PromptCharNotAllowed;
  2131.                     return false;
  2132.                 }
  2133.             }
  2134.            
  2135.             if (input == spaceChar && this.ResetOnSpace) {
  2136.                 // Position would be reset.
  2137.                 if (IsEditPosition(charDex) && charDex.IsAssigned) {
  2138.                     resultHint = MaskedTextResultHint.SideEffect;
  2139.                 }
  2140.                 else {
  2141.                     resultHint = MaskedTextResultHint.CharacterEscaped;
  2142.                 }
  2143.                 return true;
  2144.             }
  2145.            
  2146.            
  2147.             // Character was not escaped, now test it against the mask.
  2148.            
  2149.             // Test the character against the mask constraints. The switch tests false conditions.
  2150.             // Space char succeeds the test if the char type is optional.
  2151.             switch (this.mask[charDex.MaskPosition]) {
  2152.                 case '#':
  2153.                     // digit or plus/minus sign optional.
  2154.                     if (!Char.IsDigit(input) && (input != '-') && (input != '+') && input != spaceChar) {
  2155.                         resultHint = MaskedTextResultHint.DigitExpected;
  2156.                         return false;
  2157.                     }
  2158.                     break;
  2159.                 case '0':
  2160.                    
  2161.                     // digit required.
  2162.                     if (!Char.IsDigit(input)) {
  2163.                         resultHint = MaskedTextResultHint.DigitExpected;
  2164.                         return false;
  2165.                     }
  2166.                     break;
  2167.                 case '9':
  2168.                    
  2169.                     // digit optional.
  2170.                     if (!Char.IsDigit(input) && input != spaceChar) {
  2171.                         resultHint = MaskedTextResultHint.DigitExpected;
  2172.                         return false;
  2173.                     }
  2174.                     break;
  2175.                 case 'L':
  2176.                    
  2177.                     // letter required.
  2178.                     if (!Char.IsLetter(input)) {
  2179.                         resultHint = MaskedTextResultHint.LetterExpected;
  2180.                         return false;
  2181.                     }
  2182.                     if (!IsAsciiLetter(input) && this.AsciiOnly) {
  2183.                         resultHint = MaskedTextResultHint.AsciiCharacterExpected;
  2184.                         return false;
  2185.                     }
  2186.                     break;
  2187.                 case '?':
  2188.                    
  2189.                     // letter optional.
  2190.                     if (!Char.IsLetter(input) && input != spaceChar) {
  2191.                         resultHint = MaskedTextResultHint.LetterExpected;
  2192.                         return false;
  2193.                     }
  2194.                     if (!IsAsciiLetter(input) && this.AsciiOnly) {
  2195.                         resultHint = MaskedTextResultHint.AsciiCharacterExpected;
  2196.                         return false;
  2197.                     }
  2198.                     break;
  2199.                 case '&':
  2200.                    
  2201.                     // any character required.
  2202.                     if (!IsAscii(input) && this.AsciiOnly) {
  2203.                         resultHint = MaskedTextResultHint.AsciiCharacterExpected;
  2204.                         return false;
  2205.                     }
  2206.                     break;
  2207.                 case 'C':
  2208.                    
  2209.                     // any character optional.
  2210.                     if ((!IsAscii(input) && this.AsciiOnly) && input != spaceChar) {
  2211.                         resultHint = MaskedTextResultHint.AsciiCharacterExpected;
  2212.                         return false;
  2213.                     }
  2214.                     break;
  2215.                 case 'A':
  2216.                    
  2217.                     // Alphanumeric required.
  2218.                     if (!IsAlphanumeric(input)) {
  2219.                         resultHint = MaskedTextResultHint.AlphanumericCharacterExpected;
  2220.                         return false;
  2221.                     }
  2222.                     if (!IsAciiAlphanumeric(input) && this.AsciiOnly) {
  2223.                         resultHint = MaskedTextResultHint.AsciiCharacterExpected;
  2224.                         return false;
  2225.                     }
  2226.                     break;
  2227.                 case 'a':
  2228.                    
  2229.                     // Alphanumeric optional.
  2230.                     if (!IsAlphanumeric(input) && input != spaceChar) {
  2231.                         resultHint = MaskedTextResultHint.AlphanumericCharacterExpected;
  2232.                         return false;
  2233.                     }
  2234.                     if (!IsAciiAlphanumeric(input) && this.AsciiOnly) {
  2235.                         resultHint = MaskedTextResultHint.AsciiCharacterExpected;
  2236.                         return false;
  2237.                     }
  2238.                     break;
  2239.                 default:
  2240.                    
  2241.                     Debug.Fail("Invalid mask language character.");
  2242.                     break;
  2243.             }
  2244.            
  2245.             // Test passed.
  2246.            
  2247.             // setting char would not make any difference
  2248.             if (input == this.testString[position] && charDex.IsAssigned) {
  2249.                 resultHint = MaskedTextResultHint.NoEffect;
  2250.             }
  2251.             else {
  2252.                 resultHint = MaskedTextResultHint.Success;
  2253.             }
  2254.            
  2255.             return true;
  2256.         }
  2257.        
  2258.         /// <devdoc>
  2259.         /// Tests if the character at the specified position in the test string is to be escaped.
  2260.         /// Returns true on success, false otherwise.
  2261.         /// </devdoc>
  2262.         private bool TestEscapeChar(char input, int position)
  2263.         {
  2264.             CharDescriptor chDex = this.stringDescriptor[position];
  2265.             return TestEscapeChar(input, position, chDex);
  2266.            
  2267.         }
  2268.         private bool TestEscapeChar(char input, int position, CharDescriptor charDex)
  2269.         {
  2270.             if (IsLiteralPosition(charDex)) {
  2271.                 return this.SkipLiterals && input == this.testString[position];
  2272.             }
  2273.            
  2274.             if ((this.ResetOnPrompt && (input == this.promptChar)) || (this.ResetOnSpace && (input == spaceChar))) {
  2275.                 return true;
  2276.             }
  2277.            
  2278.             return false;
  2279.         }
  2280.        
  2281.         /// <devdoc>
  2282.         /// Tests if the character at the specified position in the test string can be set to the value specified,
  2283.         /// and sets the character to that value if the test is successful.
  2284.         /// The position specified is relative to the test string.
  2285.         /// The MaskedTextResultHint out param gives more information about the operation result.
  2286.         /// Returns true on success, false otherwise.
  2287.         /// </devdoc>
  2288.         private bool TestSetChar(char input, int position, out MaskedTextResultHint resultHint)
  2289.         {
  2290.             if (TestChar(input, position, out resultHint)) {
  2291.                 // the character is not to be escaped.
  2292.                 if (resultHint == MaskedTextResultHint.Success || resultHint == MaskedTextResultHint.SideEffect) {
  2293.                     SetChar(input, position);
  2294.                 }
  2295.                
  2296.                 return true;
  2297.             }
  2298.            
  2299.             return false;
  2300.         }
  2301.        
  2302.         /// <devdoc>
  2303.         /// Test the characters in the specified string agaist the test string, starting from the specified position.
  2304.         /// If the test is successful, the characters in the test string are set appropriately.
  2305.         /// On exit the testPosition contains last position where the primary operation was actually performed if successful,
  2306.         /// otherwise the first position that made the test fail. This position is relative to the test string.
  2307.         /// The MaskedTextResultHint out param gives more information about the operation result.
  2308.         /// Returns true on success, false otherwise.
  2309.         /// </devdoc>
  2310.         private bool TestSetString(string input, int position, out int testPosition, out MaskedTextResultHint resultHint)
  2311.         {
  2312.             if (TestString(input, position, out testPosition, out resultHint)) {
  2313.                 SetString(input, position);
  2314.                 return true;
  2315.             }
  2316.            
  2317.             return false;
  2318.         }
  2319.        
  2320.         /// <devdoc>
  2321.         /// Test the characters in the specified string agaist the test string, starting from the specified position.
  2322.         /// On exit the testPosition contains last position where the primary operation was actually performed if successful,
  2323.         /// otherwise the first position that made the test fail. This position is relative to the test string.
  2324.         /// The successCount out param contains the number of characters that would be actually set (not escaped).
  2325.         /// The MaskedTextResultHint out param gives more information about the operation result.
  2326.         /// Returns true on success, false otherwise.
  2327.         /// </devdoc>
  2328.         private bool TestString(string input, int position, out int testPosition, out MaskedTextResultHint resultHint)
  2329.         {
  2330.             Debug.Assert(input != null, "null input.");
  2331.             Debug.Assert(position >= 0, "Position out of range.");
  2332.            
  2333.             resultHint = MaskedTextResultHint.Unknown;
  2334.             testPosition = position;
  2335.            
  2336.             if (input.Length == 0) {
  2337.                 return true;
  2338.             }
  2339.            
  2340.             // If any char is actually accepted, then the hint is success, otherwise whatever the last character result is.
  2341.             // Need a temp variable for this.
  2342.             MaskedTextResultHint tempHint = resultHint;
  2343.            
  2344.             foreach (char ch in input) {
  2345.                 if (testPosition >= this.testString.Length) {
  2346.                     resultHint = MaskedTextResultHint.UnavailableEditPosition;
  2347.                     return false;
  2348.                 }
  2349.                
  2350.                 // If character is not to be escaped, we need to find an edit position to test it in.
  2351.                 if (!TestEscapeChar(ch, testPosition)) {
  2352.                     testPosition = FindEditPositionFrom(testPosition, forward);
  2353.                    
  2354.                     if (testPosition == invalidIndex) {
  2355.                         testPosition = this.testString.Length;
  2356.                         resultHint = MaskedTextResultHint.UnavailableEditPosition;
  2357.                         return false;
  2358.                     }
  2359.                 }
  2360.                
  2361.                 // Test/Set char will scape prompt, space and literals if needed.
  2362.                 if (!TestChar(ch, testPosition, out tempHint)) {
  2363.                     resultHint = tempHint;
  2364.                     return false;
  2365.                 }
  2366.                
  2367.                 // Result precedence: Success, SideEffect, NoEffect, CharacterEscaped.
  2368.                 if (tempHint > resultHint) {
  2369.                     resultHint = tempHint;
  2370.                 }
  2371.                
  2372.                 testPosition++;
  2373.             }
  2374.            
  2375.             testPosition--;
  2376.            
  2377.             return true;
  2378.         }
  2379.        
  2380.         /// <devdoc>
  2381.         /// Returns a formatted string based on the mask, honoring only the PasswordChar property. prompt character
  2382.         /// and literals are always included. This is the text to be shown in a control when it has the focus.
  2383.         /// </devdoc>
  2384.         public string ToDisplayString()
  2385.         {
  2386.             // just return the testString since it contains the formatted text.
  2387.             if (!this.IsPassword || this.assignedCharCount == 0) {
  2388.                 return this.testString.ToString();
  2389.             }
  2390.            
  2391.             // Copy test string and replace edit chars with password.
  2392.             StringBuilder st = new StringBuilder(this.testString.Length);
  2393.            
  2394.             for (int position = 0; position < this.testString.Length; position++) {
  2395.                 CharDescriptor chDex = this.stringDescriptor[position];
  2396.                 st.Append(IsEditPosition(chDex) && chDex.IsAssigned ? this.passwordChar : this.testString[position]);
  2397.             }
  2398.            
  2399.             return st.ToString();
  2400.         }
  2401.        
  2402.         /// <devdoc>
  2403.         /// Returns a formatted string based on the mask, honoring IncludePrompt and IncludeLiterals but ignoring
  2404.         /// PasswordChar.
  2405.         /// </devdoc>
  2406.         public override string ToString()
  2407.         {
  2408.                 /*ignorePwdChar*/            return ToString(true, this.IncludePrompt, this.IncludeLiterals, 0, this.testString.Length);
  2409.         }
  2410.        
  2411.         /// <devdoc>
  2412.         /// Returns a formatted string based on the mask, honoring the IncludePrompt and IncludeLiterals properties,
  2413.         /// and PasswordChar depending on the value of the 'ignorePasswordChar' parameter.
  2414.         /// </devdoc>
  2415.         public string ToString(bool ignorePasswordChar)
  2416.         {
  2417.             return ToString(ignorePasswordChar, this.IncludePrompt, this.IncludeLiterals, 0, this.testString.Length);
  2418.         }
  2419.        
  2420.         /// <devdoc>
  2421.         /// Returns a formatted string starting at the specified position and for the specified number of character,
  2422.         /// based on the mask, honoring IncludePrompt and IncludeLiterals but ignoring PasswordChar.
  2423.         /// Parameters are relative to the test string.
  2424.         /// </devdoc>
  2425.         public string ToString(int startPosition, int length)
  2426.         {
  2427.                 /*ignorePwdChar*/            return ToString(true, this.IncludePrompt, this.IncludeLiterals, startPosition, length);
  2428.         }
  2429.        
  2430.         /// <devdoc>
  2431.         /// Returns a formatted string starting at the specified position and for the specified number of character,
  2432.         /// based on the mask, honoring the IncludePrompt, IncludeLiterals properties and PasswordChar depending on
  2433.         /// the 'ignorePasswordChar' parameter.
  2434.         /// Parameters are relative to the test string.
  2435.         /// </devdoc>
  2436.         public string ToString(bool ignorePasswordChar, int startPosition, int length)
  2437.         {
  2438.             return ToString(ignorePasswordChar, this.IncludePrompt, this.IncludeLiterals, startPosition, length);
  2439.         }
  2440.        
  2441.         /// <devdoc>
  2442.         /// Returns a formatted string based on the mask, ignoring the PasswordChar and according to the includePrompt
  2443.         /// and includeLiterals parameters.
  2444.         /// </devdoc>
  2445.         public string ToString(bool includePrompt, bool includeLiterals)
  2446.         {
  2447.                 /*ignorePwdChar*/            return ToString(true, includePrompt, includeLiterals, 0, this.testString.Length);
  2448.         }
  2449.        
  2450.         /// <devdoc>
  2451.         /// Returns a formatted string starting at the specified position and for the specified number of character,
  2452.         /// based on the mask, according to the ignorePasswordChar, includePrompt and includeLiterals parameters.
  2453.         /// Parameters are relative to the test string.
  2454.         /// </devdoc>
  2455.         public string ToString(bool includePrompt, bool includeLiterals, int startPosition, int length)
  2456.         {
  2457.                 /*ignorePwdChar*/            return ToString(true, includePrompt, includeLiterals, startPosition, length);
  2458.         }
  2459.        
  2460.         /// <devdoc>
  2461.         /// Returns a formatted string starting at the specified position and for the specified number of character,
  2462.         /// based on the mask, according to the ignorePasswordChar, includePrompt and includeLiterals parameters.
  2463.         /// Parameters are relative to the test string.
  2464.         /// </devdoc>
  2465.         public string ToString(bool ignorePasswordChar, bool includePrompt, bool includeLiterals, int startPosition, int length)
  2466.         {
  2467.             if (length <= 0) {
  2468.                 return string.Empty;
  2469.             }
  2470.            
  2471.             if (startPosition < 0) {
  2472.                 startPosition = 0;
  2473.                 //throw new ArgumentOutOfRangeException("startPosition");
  2474.             }
  2475.            
  2476.             if (startPosition >= this.testString.Length) {
  2477.                 return string.Empty;
  2478.                 //throw new ArgumentOutOfRangeException("startPosition");
  2479.             }
  2480.            
  2481.             int maxLength = this.testString.Length - startPosition;
  2482.            
  2483.             if (length > maxLength) {
  2484.                 length = maxLength;
  2485.                 //throw new ArgumentOutOfRangeException("length");
  2486.             }
  2487.            
  2488.             // we may not need to format the text...
  2489.             if (!this.IsPassword || ignorePasswordChar) {
  2490.                 if (includePrompt && includeLiterals) {
  2491.                     return this.testString.ToString(startPosition, length);
  2492.                     // testString contains just what the user is asking for.
  2493.                 }
  2494.             }
  2495.            
  2496.             // Build the formatted string ...
  2497.            
  2498.             StringBuilder st = new StringBuilder();
  2499.             int lastPosition = startPosition + length - 1;
  2500.            
  2501.             if (!includePrompt) {
  2502.                 // If prompt is not to be included we need to replace it with a space, but only for unassigned postions below
  2503.                 // the last assigned position or last literal position if including literals, whichever is higher; upper unassigned
  2504.                 // positions are not included in the resulting string.
  2505.                
  2506.                 int lastLiteralPos = includeLiterals ? FindNonEditPositionInRange(startPosition, lastPosition, backward) : InvalidIndex;
  2507.                 int lastAssignedPos = FindAssignedEditPositionInRange(lastLiteralPos == InvalidIndex ? startPosition : lastLiteralPos, lastPosition, backward);
  2508.                
  2509.                 // If lastLiteralPos is in the range and lastAssignedPos is not InvalidIndex, the lastAssignedPos is the upper limit
  2510.                 // we are looking for since it is searched in the range from lastLiteralPos and lastPosition. In any other case
  2511.                 // lastLiteral would contain the upper position we are looking for or InvalidIndex, meaning all characters in the
  2512.                 // range are to be ignored, in this case a null string should be returned.
  2513.                
  2514.                 lastPosition = lastAssignedPos != InvalidIndex ? lastAssignedPos : lastLiteralPos;
  2515.                
  2516.                 if (lastPosition == InvalidIndex) {
  2517.                     return string.Empty;
  2518.                 }
  2519.             }
  2520.            
  2521.             for (int index = startPosition; index <= lastPosition; index++) {
  2522.                 char ch = this.testString[index];
  2523.                 CharDescriptor chDex = this.stringDescriptor[index];
  2524.                
  2525.                 switch (chDex.CharType) {
  2526.                     case CharType.EditOptional:
  2527.                     case CharType.EditRequired:
  2528.                         if (chDex.IsAssigned) {
  2529.                             if (this.IsPassword && !ignorePasswordChar) {
  2530.                                 st.Append(this.passwordChar);
  2531.                                 // replace edit char with pwd char.
  2532.                                 break;
  2533.                             }
  2534.                         }
  2535.                         else {
  2536.                             if (!includePrompt) {
  2537.                                 st.Append(spaceChar);
  2538.                                 // replace prompt with space.
  2539.                                 break;
  2540.                             }
  2541.                         }
  2542.                        
  2543.                         goto default;
  2544.                         break;
  2545.                     case CharType.Separator:
  2546.                     case CharType.Literal:
  2547.                        
  2548.                         if (!includeLiterals) {
  2549.                             break;
  2550.                             // exclude literals.
  2551.                         }
  2552.                         goto default;
  2553.                         break;
  2554.                     default:
  2555.                        
  2556.                         st.Append(ch);
  2557.                         break;
  2558.                 }
  2559.             }
  2560.            
  2561.             return st.ToString();
  2562.         }
  2563.        
  2564.         /// <devdoc>
  2565.         /// Tests whether the specified character would be set successfully at the specified position.
  2566.         /// </devdoc>
  2567.         public bool VerifyChar(char input, int position, out MaskedTextResultHint hint)
  2568.         {
  2569.             hint = MaskedTextResultHint.NoEffect;
  2570.            
  2571.             if (position < 0 || position >= this.testString.Length) {
  2572.                 hint = MaskedTextResultHint.PositionOutOfRange;
  2573.                 return false;
  2574.             }
  2575.            
  2576.             return TestChar(input, position, out hint);
  2577.         }
  2578.        
  2579.         /// <devdoc>
  2580.         /// Tests whether the specified character would be escaped at the specified position.
  2581.         /// </devdoc>
  2582.         public bool VerifyEscapeChar(char input, int position)
  2583.         {
  2584.             if (position < 0 || position >= this.testString.Length) {
  2585.                 return false;
  2586.             }
  2587.            
  2588.             return TestEscapeChar(input, position);
  2589.         }
  2590.        
  2591.         /// <devdoc>
  2592.         /// Verifies the test string against the mask.
  2593.         /// </devdoc>
  2594.         public bool VerifyString(string input)
  2595.         {
  2596.             int dummyVar;
  2597.             MaskedTextResultHint dummyVar2;
  2598.             return VerifyString(input, out dummyVar, out dummyVar2);
  2599.         }
  2600.        
  2601.         /// <devdoc>
  2602.         /// Verifies the test string against the mask.
  2603.         /// On exit the testPosition contains last position where the primary operation was actually performed if successful,
  2604.         /// otherwise the first position that made the test fail. This position is relative to the test string.
  2605.         /// The MaskedTextResultHint out param gives more information about the operation result.
  2606.         /// Returns true on success, false otherwise.
  2607.         /// </devdoc>
  2608.         public bool VerifyString(string input, out int testPosition, out MaskedTextResultHint resultHint)
  2609.         {
  2610.             testPosition = 0;
  2611.            
  2612.             // nothing to verify.
  2613.             if (input == null || input.Length == 0) {
  2614.                 resultHint = MaskedTextResultHint.NoEffect;
  2615.                 return true;
  2616.             }
  2617.            
  2618.             return TestString(input, 0, out testPosition, out resultHint);
  2619.         }
  2620.     }
  2621. }

Developer Fusion