Merge remote-tracking branch 'joncham/sgen-msvc2'
[mono.git] / mcs / class / System / System.ComponentModel / MaskedTextProvider.cs
1 // Permission is hereby granted, free of charge, to any person obtaining
2 // a copy of this software and associated documentation files (the
3 // "Software"), to deal in the Software without restriction, including
4 // without limitation the rights to use, copy, modify, merge, publish,
5 // distribute, sublicense, and/or sell copies of the Software, and to
6 // permit persons to whom the Software is furnished to do so, subject to
7 // the following conditions:
8 // 
9 // The above copyright notice and this permission notice shall be
10 // included in all copies or substantial portions of the Software.
11 // 
12 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
13 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
14 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
15 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
16 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
17 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
18 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
19 //
20 // Copyright (c) 2007 Novell, Inc. (http://www.novell.com)
21 //
22 // Authors:
23 //      Rolf Bjarne Kvinge  <RKvinge@novell.com>
24 //
25 //
26
27 /*
28
29 Mask language:
30
31 0       Digit, required. This element will accept any single digit between 0 and 9.
32 9       Digit or space, optional. 
33 #       Digit or space, optional. If this position is blank in the mask, it will be rendered as a space in the Text property. Plus (+) and minus (-) signs are allowed.
34 L       Letter, required. Restricts input to the ASCII letters a-z and A-Z. This mask element is equivalent to [a-zA-Z] in regular expressions. 
35 ?       Letter, optional. Restricts input to the ASCII letters a-z and A-Z. This mask element is equivalent to [a-zA-Z]? in regular expressions. 
36 &       Character, required. If the AsciiOnly property is set to true, this element behaves like the "L" element. 
37 C       Character, optional. Any non-control character. If the AsciiOnly property is set to true, this element behaves like the "?" element.
38  * LAMESPEC: A is REQUIRED, not optional.
39 A       Alphanumeric, optional. If the AsciiOnly property is set to true, the only characters it will accept are the ASCII letters a-z and A-Z.
40 a       Alphanumeric, optional. If the AsciiOnly property is set to true, the only characters it will accept are the ASCII letters a-z and A-Z.
41 .       Decimal placeholder. The actual display character used will be the decimal symbol appropriate to the format provider, as determined by the control's FormatProvider property.
42 ,       Thousands placeholder. The actual display character used will be the thousands placeholder appropriate to the format provider, as determined by the control's FormatProvider property.
43 :       Time separator. The actual display character used will be the time symbol appropriate to the format provider, as determined by the control's FormatProvider property.
44 /       Date separator. The actual display character used will be the date symbol appropriate to the format provider, as determined by the control's FormatProvider property.
45 $       Currency symbol. The actual character displayed will be the currency symbol appropriate to the format provider, as determined by the control's FormatProvider property.
46 <       Shift down. Converts all characters that follow to lowercase. 
47 >       Shift up. Converts all characters that follow to uppercase.
48 |       Disable a previous shift up or shift down.
49 \       Escape. Escapes a mask character, turning it into a literal. "\\" is the escape sequence for a backslash.
50
51  * */
52  
53 using System.Globalization;
54 using System.Collections;
55 using System.Diagnostics;
56 using System.Text;
57
58 namespace System.ComponentModel {
59         public class MaskedTextProvider : ICloneable
60         {
61 #region Private fields
62                 private bool allow_prompt_as_input;
63                 private bool ascii_only;
64                 private CultureInfo culture; 
65                 private bool include_literals;
66                 private bool include_prompt;
67                 private bool is_password;
68                 private string mask;
69                 private char password_char;
70                 private char prompt_char;
71                 private bool reset_on_prompt;
72                 private bool reset_on_space;
73                 private bool skip_literals;
74                 
75                 private EditPosition [] edit_positions;
76                 
77                 const char default_prompt_char = '_';
78                 const char default_password_char = char.MinValue;
79                 
80 #endregion
81
82 #region Private classes
83                 private enum EditState {
84                         None,
85                         UpperCase,
86                         LowerCase
87                 }
88                 private enum EditType {
89                         DigitRequired, // 0
90                         DigitOrSpaceOptional, // 9
91                         DigitOrSpaceOptional_Blank, // #
92                         LetterRequired, // L
93                         LetterOptional, // ?
94                         CharacterRequired, // &
95                         CharacterOptional, // C
96                         AlphanumericRequired, // A
97                         AlphanumericOptional, // a
98                         DecimalPlaceholder, // .
99                         ThousandsPlaceholder, // ,
100                         TimeSeparator, // :
101                         DateSeparator, // /
102                         CurrencySymbol, // $ 
103                         Literal
104                 }
105
106                 private class EditPosition {
107                         public MaskedTextProvider Parent;
108                         public EditType Type;
109                         public EditState State;
110                         public char MaskCharacter;
111                         public char input;
112                         
113                         public void Reset ()
114                         {
115                                 input = char.MinValue;
116                         }
117                         
118                         internal EditPosition Clone () {
119                                 EditPosition result = new EditPosition ();
120                                 result.Parent = Parent;
121                                 result.Type = Type;
122                                 result.State = State;
123                                 result.MaskCharacter = MaskCharacter;
124                                 result.input = input;
125                                 return result;
126                         }
127                         
128                         public char Input {
129                                 get {
130                                         return input;
131                                 }
132                                 set {
133                                         switch (State) {
134                                         case EditState.LowerCase:
135                                                 input = char.ToLower (value, Parent.Culture);
136                                                 break;
137                                         case EditState.UpperCase:
138                                                 input = char.ToUpper (value, Parent.Culture);
139                                                 break;
140                                         default:// case EditState.None:
141                                                 input = value;
142                                                 break;
143                                         }
144                                 }
145                         }
146                         
147                         public bool IsAscii (char c)
148                         {
149                                 return ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z'));
150                         }
151                         
152                         public bool Match (char c, out MaskedTextResultHint resultHint, bool only_test)
153                         {
154                                 if (!MaskedTextProvider.IsValidInputChar (c)) {
155                                         resultHint = MaskedTextResultHint.InvalidInput;
156                                         return false;
157                                 }
158
159                                 if (Parent.ResetOnSpace && c == ' ' && Editable) {
160                                         resultHint = MaskedTextResultHint.CharacterEscaped;
161                                         if (FilledIn) {
162                                                 resultHint = MaskedTextResultHint.Success;
163                                                 if (!only_test && input != ' ') {
164                                                         switch (Type) {
165                                                         case EditType.AlphanumericOptional:
166                                                         case EditType.AlphanumericRequired:
167                                                         case EditType.CharacterOptional:
168                                                         case EditType.CharacterRequired:
169                                                                 Input = c;
170                                                                 break;
171                                                         default:
172                                                                 Input = char.MinValue;
173                                                                 break;
174                                                         }
175                                                 }
176                                         }
177                                         return true;
178                                 }
179                                 
180                                 if (Type == EditType.Literal && MaskCharacter == c && Parent.SkipLiterals) {
181                                         resultHint = MaskedTextResultHint.Success;
182                                         return true;
183                                 }
184                                 
185                                 if (!Editable) {
186                                         resultHint = MaskedTextResultHint.NonEditPosition;
187                                         return false;
188                                 }
189
190                                 switch (Type) {
191                                         case EditType.AlphanumericOptional:
192                                         case EditType.AlphanumericRequired:
193                                                 if (char.IsLetterOrDigit (c)) {
194                                                         if (Parent.AsciiOnly && !IsAscii (c)) {
195                                                                 resultHint = MaskedTextResultHint.AsciiCharacterExpected;
196                                                                 return false;
197                                                         } else {
198                                                                 if (!only_test) {
199                                                                         Input = c;
200                                                                 }
201                                                                 resultHint = MaskedTextResultHint.Success;
202                                                                 return true;
203                                                         }
204                                                 } else {
205                                                         resultHint = MaskedTextResultHint.AlphanumericCharacterExpected;
206                                                         return false;
207                                                 }
208                                         case EditType.CharacterOptional:
209                                         case EditType.CharacterRequired:
210                                                 if (Parent.AsciiOnly && !IsAscii (c)) {
211                                                         resultHint = MaskedTextResultHint.LetterExpected;
212                                                         return false;
213                                                 } else if (!char.IsControl (c)) {
214                                                         if (!only_test) {
215                                                                 Input = c;
216                                                         }
217                                                         resultHint = MaskedTextResultHint.Success;
218                                                         return true;
219                                                 } else {
220                                                         resultHint = MaskedTextResultHint.LetterExpected;
221                                                         return false;
222                                                 }
223                                         case EditType.DigitOrSpaceOptional:
224                                         case EditType.DigitOrSpaceOptional_Blank:
225                                                 if (char.IsDigit (c) || c == ' ') {
226                                                         if (!only_test) {
227                                                                 Input = c;
228                                                         }
229                                                         resultHint = MaskedTextResultHint.Success;
230                                                         return true;
231                                                 } else {
232                                                         resultHint = MaskedTextResultHint.DigitExpected;
233                                                         return false;
234                                                 }
235                                         case EditType.DigitRequired:
236                                                 if (char.IsDigit (c)) {
237                                                         if (!only_test) {
238                                                                 Input = c;
239                                                         }
240                                                         resultHint = MaskedTextResultHint.Success;
241                                                         return true;
242                                                 } else {
243                                                         resultHint = MaskedTextResultHint.DigitExpected;
244                                                         return false;
245                                                 }
246                                         case EditType.LetterOptional:
247                                         case EditType.LetterRequired:
248                                                 if (!char.IsLetter (c)) {
249                                                         resultHint = MaskedTextResultHint.LetterExpected;
250                                                         return false;
251                                                 } else if (Parent.AsciiOnly && !IsAscii (c)) {
252                                                         resultHint = MaskedTextResultHint.LetterExpected;
253                                                         return false;
254                                                 } else {
255                                                         if (!only_test) {
256                                                                 Input = c;
257                                                         }
258                                                         resultHint = MaskedTextResultHint.Success;
259                                                         return true;
260                                                 }
261                                         default:
262                                                 resultHint = MaskedTextResultHint.Unknown;
263                                                 return false;
264                                 }
265                         }
266                         
267                         public bool FilledIn {
268                                 get {
269                                         return Input != char.MinValue;
270                                 }
271                         }
272                         
273                         public bool Required {
274                                 get {
275                                         switch (MaskCharacter) {
276                                         case '0':
277                                         case 'L':
278                                         case '&':
279                                         case 'A':
280                                                 return true;
281                                         default:
282                                                 return false;
283                                         }
284                                 }
285                         }
286                         
287                         public bool Editable {
288                                 get {
289                                         switch (MaskCharacter) {
290                                         case '0':
291                                         case '9':
292                                         case '#':
293                                         case 'L':
294                                         case '?':
295                                         case '&':
296                                         case 'C':
297                                         case 'A':
298                                         case 'a':
299                                                 return true;
300                                         default:
301                                                 return false;
302                                         }
303                                 }
304                         }       
305                         
306                         public bool Visible {
307                                 get {
308                                         switch (MaskCharacter) {
309                                         case '|':
310                                         case '<':
311                                         case '>':
312                                                 return false;
313                                         default:
314                                                 return true;
315                                         }
316                                 }
317                         }
318                         public string Text {
319                                 get {
320                                         if (Type == EditType.Literal) {
321                                                 return MaskCharacter.ToString ();
322                                         }
323                                         switch (MaskCharacter) {
324                                         case '.': return Parent.Culture.NumberFormat.NumberDecimalSeparator;
325                                         case ',': return Parent.Culture.NumberFormat.NumberGroupSeparator;
326                                         case ':': return Parent.Culture.DateTimeFormat.TimeSeparator;
327                                         case '/': return Parent.Culture.DateTimeFormat.DateSeparator;
328                                         case '$': return Parent.Culture.NumberFormat.CurrencySymbol;
329                                         default:
330                                                 return FilledIn ? Input.ToString () : Parent.PromptChar.ToString ();
331                                         }
332                                 }
333                         }
334
335                         private EditPosition ()
336                         {
337                         }
338
339                         public EditPosition (MaskedTextProvider Parent, EditType Type, EditState State, char MaskCharacter)
340                         {
341                                 this.Type = Type;
342                                 this.Parent = Parent;
343                                 this.State = State;
344                                 this.MaskCharacter = MaskCharacter;
345                                 
346                         }
347                 }
348 #endregion
349
350 #region Private methods & properties
351                 private void SetMask (string mask)
352                 {
353                         if (mask == null || mask == string.Empty)
354                                 throw new ArgumentException ("The Mask value cannot be null or empty.\r\nParameter name: mask");
355
356                         this.mask = mask;
357                         
358                         System.Collections.Generic.List <EditPosition> list = new System.Collections.Generic.List<EditPosition> (mask.Length);
359                         
360                         EditState State = EditState.None;
361                         bool escaped = false;
362                         for (int i = 0; i < mask.Length; i++) {
363                                 if (escaped) {
364                                         list.Add (new EditPosition (this, EditType.Literal, State, mask [i]));
365                                         escaped = false;
366                                         continue;
367                                 }
368                                 
369                                 switch (mask [i]) {
370                                 case '\\':
371                                         escaped = true; 
372                                         break;
373                                 case '0':
374                                         list.Add (new EditPosition (this, EditType.DigitRequired, State, mask [i])); 
375                                         break;
376                                 case '9':
377                                         list.Add (new EditPosition (this, EditType.DigitOrSpaceOptional, State, mask [i]));
378                                         break;
379                                 case '#':
380                                         list.Add (new EditPosition (this, EditType.DigitOrSpaceOptional_Blank, State, mask [i]));
381                                         break;
382                                 case 'L':
383                                         list.Add (new EditPosition (this, EditType.LetterRequired, State, mask [i]));
384                                         break;
385                                 case '?':
386                                         list.Add (new EditPosition (this, EditType.LetterOptional, State, mask [i]));
387                                         break;
388                                 case '&':
389                                         list.Add (new EditPosition (this, EditType.CharacterRequired, State, mask [i]));
390                                         break;
391                                 case 'C':
392                                         list.Add (new EditPosition (this, EditType.CharacterOptional, State, mask [i]));
393                                         break;
394                                 case 'A':
395                                         list.Add (new EditPosition (this, EditType.AlphanumericRequired, State, mask [i]));
396                                         break;
397                                 case 'a':
398                                         list.Add (new EditPosition (this, EditType.AlphanumericOptional, State, mask [i]));
399                                         break;
400                                 case '.':
401                                         list.Add (new EditPosition (this, EditType.DecimalPlaceholder, State, mask [i]));
402                                         break;
403                                 case ',':
404                                         list.Add (new EditPosition (this, EditType.ThousandsPlaceholder, State, mask [i]));
405                                         break;
406                                 case ':':
407                                         list.Add (new EditPosition (this, EditType.TimeSeparator, State, mask [i]));
408                                         break;
409                                 case '/':
410                                         list.Add (new EditPosition (this, EditType.DateSeparator, State, mask [i]));
411                                         break;
412                                 case '$':
413                                         list.Add (new EditPosition (this, EditType.CurrencySymbol, State, mask [i]));
414                                         break;
415                                 case '<':
416                                         State = EditState.LowerCase;
417                                         break;
418                                 case '>':
419                                         State = EditState.UpperCase;
420                                         break;
421                                 case '|':
422                                         State = EditState.None;
423                                         break;
424                                 default:
425                                         list.Add (new EditPosition (this, EditType.Literal, State, mask [i]));
426                                         break;
427                                 }
428                         }
429                         edit_positions = list.ToArray ();
430                 }
431                 
432                 private EditPosition [] ClonePositions ()
433                 {
434                         EditPosition [] result = new EditPosition [edit_positions.Length];
435                         for (int i = 0; i < result.Length; i++) {
436                                 result [i] = edit_positions [i].Clone ();
437                         }
438                         return result;
439                 }
440
441                 private bool AddInternal (string str_input, out int testPosition, out MaskedTextResultHint resultHint, bool only_test)
442                 {
443                         EditPosition [] edit_positions;
444                         
445                         if (only_test) {
446                                 edit_positions = ClonePositions ();
447                         } else {
448                                 edit_positions = this.edit_positions;
449                         }
450
451                         if (str_input == null)
452                                 throw new ArgumentNullException ("input");
453
454                         if (str_input.Length == 0) {
455                                 resultHint = MaskedTextResultHint.NoEffect;
456                                 testPosition = LastAssignedPosition + 1;
457                                 return true;
458                         }
459
460                         resultHint = MaskedTextResultHint.Unknown;
461                         testPosition = 0;
462
463                         int next_position = LastAssignedPosition;
464                         MaskedTextResultHint tmpResult = MaskedTextResultHint.Unknown;
465
466                         if (next_position >= edit_positions.Length) {
467                                 testPosition = next_position;
468                                 resultHint = MaskedTextResultHint.UnavailableEditPosition;
469                                 return false;
470                         }
471
472                         for (int i = 0; i < str_input.Length; i++) {
473                                 char input = str_input [i];
474                                 next_position++;
475                                 testPosition = next_position;
476                                 
477                                 if (tmpResult > resultHint) {
478                                         resultHint = tmpResult;
479                                 }
480
481                                 if (VerifyEscapeChar (input, next_position)) {
482                                         tmpResult = MaskedTextResultHint.CharacterEscaped;
483                                         continue;
484                                 }
485
486                                 next_position = FindEditPositionFrom (next_position, true);
487                                 testPosition = next_position;
488
489                                 if (next_position == InvalidIndex) {
490                                         testPosition = edit_positions.Length;
491                                         resultHint = MaskedTextResultHint.UnavailableEditPosition;
492                                         return false;
493                                 }
494
495                                 if (!IsValidInputChar (input)) {
496                                         testPosition = next_position;
497                                         resultHint = MaskedTextResultHint.InvalidInput;
498                                         return false;
499                                 }
500                                 
501                                 if (!edit_positions [next_position].Match (input, out tmpResult, false)) {
502                                         testPosition = next_position;
503                                         resultHint = tmpResult;
504                                         return false;
505                                 }               
506                         }
507
508                         if (tmpResult > resultHint) {
509                                 resultHint = tmpResult;
510                         }
511
512                         return true;
513                 }
514                 
515                 private bool AddInternal (char input, out int testPosition, out MaskedTextResultHint resultHint, bool check_available_positions_first, bool check_escape_char_first)
516                 {
517                         /*
518                          * check_available_positions_first: when adding a char, MS first seems to check if there are any available edit positions, then if there are any
519                          * they will try to add the char (meaning that if you add a char matching a literal char in the mask with SkipLiterals and there are no
520                          * more available edit positions, it will fail).
521                          * 
522                          * check_escape_char_first: When adding a char, MS doesn't check for escape char, they directly search for the first edit position.
523                          * When adding a string, they check first for escape char, then if not successful they find the first edit position.
524                          */
525                          
526                         int new_position;
527                         testPosition = 0;
528                         
529                         new_position = LastAssignedPosition + 1;
530
531                         if (check_available_positions_first) {
532                                 int tmp = new_position;
533                                 bool any_available = false;
534                                 while (tmp < edit_positions.Length) {
535                                         if (edit_positions [tmp].Editable) {
536                                                 any_available = true;
537                                                 break;
538                                         }
539                                         tmp++;
540                                 }
541                                 if (!any_available) {
542                                         testPosition = tmp;
543                                         resultHint = MaskedTextResultHint.UnavailableEditPosition;
544                                         return GetOperationResultFromHint (resultHint);
545                                 }
546                         }
547
548                         if (check_escape_char_first) {
549                                 if (VerifyEscapeChar (input, new_position)) {
550                                         testPosition = new_position;
551                                         resultHint = MaskedTextResultHint.CharacterEscaped;
552                                         return true;
553                                 }
554                         }
555
556                         new_position = FindEditPositionFrom (new_position, true);
557
558                         if (new_position > edit_positions.Length - 1 ||new_position == InvalidIndex) {
559                                 testPosition = new_position;
560                                 resultHint = MaskedTextResultHint.UnavailableEditPosition;
561                                 return GetOperationResultFromHint (resultHint);
562                         }
563
564                         if (!IsValidInputChar (input)) {
565                                 testPosition = new_position;
566                                 resultHint = MaskedTextResultHint.InvalidInput;
567                                 return GetOperationResultFromHint (resultHint);
568                         }
569
570                         if (!edit_positions [new_position].Match (input, out resultHint, false)) {
571                                 testPosition = new_position;
572                                 return GetOperationResultFromHint (resultHint);
573                         }
574
575                         testPosition = new_position;
576
577                         return GetOperationResultFromHint (resultHint);
578                 }
579                 
580                 private bool VerifyStringInternal (string input, out int testPosition, out MaskedTextResultHint resultHint, int startIndex, bool only_test)
581                 {
582                         int previous_position = startIndex;
583                         int current_position;
584                         resultHint = MaskedTextResultHint.Unknown;
585                         
586                         // Replace existing characters
587                         for (int i = 0; i < input.Length; i++) {
588                                 MaskedTextResultHint tmpResult;
589                                 current_position = FindEditPositionFrom (previous_position, true);
590                                 if (current_position == InvalidIndex) {
591                                         testPosition = edit_positions.Length;
592                                         resultHint = MaskedTextResultHint.UnavailableEditPosition;
593                                         return false;
594                                 }
595                                 
596                                 if (!VerifyCharInternal (input [i], current_position, out tmpResult, only_test)) {
597                                         testPosition = current_position;
598                                         resultHint = tmpResult;
599                                         return false;
600                                 }
601                                 if (tmpResult > resultHint) {
602                                         resultHint = tmpResult;
603                                 }
604                                 previous_position = current_position + 1;
605                                 
606                         }
607                         // Remove characters not in the input.
608                         if (!only_test) {
609                                 previous_position = FindEditPositionFrom (previous_position, true);
610                                 while (previous_position != InvalidIndex) {
611                                         if (edit_positions [previous_position].FilledIn) {
612                                                 edit_positions [previous_position].Reset ();
613                                                 if (resultHint != MaskedTextResultHint.NoEffect) {
614                                                         resultHint = MaskedTextResultHint.Success;
615                                                 }
616                                         }
617                                         previous_position = FindEditPositionFrom (previous_position + 1, true);
618                                 }
619                         }
620                         if (input.Length > 0) {
621                                 testPosition = startIndex + input.Length - 1;
622                         } else {
623                                 testPosition = startIndex;
624                                 if (resultHint < MaskedTextResultHint.NoEffect) {
625                                         resultHint = MaskedTextResultHint.NoEffect;
626                                 }
627                         }
628                         
629                         return true;
630                 }
631
632                 private bool VerifyCharInternal (char input, int position, out MaskedTextResultHint hint, bool only_test)
633                 {
634                         hint = MaskedTextResultHint.Unknown;
635
636                         if (position < 0 || position >= edit_positions.Length) {
637                                 hint = MaskedTextResultHint.PositionOutOfRange;
638                                 return false;
639                         }
640
641                         if (!IsValidInputChar (input)) {
642                                 hint = MaskedTextResultHint.InvalidInput;
643                                 return false;
644                         }
645
646                         if (input == ' ' && ResetOnSpace && edit_positions [position].Editable && edit_positions [position].FilledIn) {
647                                 if (!only_test) {
648                                         edit_positions [position].Reset ();
649                                 }
650                                 hint = MaskedTextResultHint.SideEffect;
651                                 return true;
652                         }
653                         
654                         if (edit_positions [position].Editable &&  edit_positions [position].FilledIn && edit_positions [position].input == input) {
655                                 hint = MaskedTextResultHint.NoEffect;
656                                 return true;
657                         }
658
659                         if (SkipLiterals && !edit_positions [position].Editable && edit_positions [position].Text == input.ToString ()) {
660                                 hint = MaskedTextResultHint.CharacterEscaped;
661                                 return true;
662                         }
663
664                         return edit_positions [position].Match (input, out hint, only_test);
665                 }
666                 
667                 // Test to see if the input string can be inserted at the specified position.
668                 // Does not try to move characters.
669                 private bool IsInsertableString (string str_input, int position, out int testPosition, out MaskedTextResultHint resultHint)
670                 {
671                         int current_position = position;
672                         int test_position;
673                         
674                         resultHint = MaskedTextResultHint.UnavailableEditPosition;
675                         testPosition = InvalidIndex;
676                         
677                         for (int i = 0; i < str_input.Length; i++) {
678                                 char ch = str_input [i];
679
680                                 test_position = FindEditPositionFrom (current_position, true);
681                                 
682                                 if (test_position != InvalidIndex && VerifyEscapeChar (ch, test_position)) {
683                                         current_position = test_position + 1;
684                                         continue;
685                                 }
686                                 
687                                 if (VerifyEscapeChar (ch, current_position)) {
688                                         current_position = current_position + 1;
689                                         continue;
690                                 }
691                                 
692                                 
693                                 if (test_position == InvalidIndex) {
694                                         resultHint = MaskedTextResultHint.UnavailableEditPosition;
695                                         testPosition = edit_positions.Length;
696                                         return false;
697                                 }
698
699                                 testPosition = test_position;
700                                 
701                                 if (!edit_positions [test_position].Match (ch, out resultHint, true)) {
702                                         return false;
703                                 }
704                                 
705                                 current_position = test_position + 1;
706                         }
707                         resultHint = MaskedTextResultHint.Success;
708                         
709                         return true;
710                 }
711                 
712                 private bool ShiftPositionsRight (EditPosition [] edit_positions, int start, out int testPosition, out MaskedTextResultHint resultHint)
713                 {
714                         int index = start;
715                         int last_assigned_index = FindAssignedEditPositionFrom (edit_positions.Length, false);
716                         int endindex = FindUnassignedEditPositionFrom (last_assigned_index, true); // Find the first non-assigned edit position
717                         
718                         testPosition = start;
719                         resultHint = MaskedTextResultHint.Unknown;
720
721                         if (endindex == InvalidIndex) {
722                                 // No more free edit positions.
723                                 testPosition = edit_positions.Length;
724                                 resultHint = MaskedTextResultHint.UnavailableEditPosition;
725                                 return false;
726                         }
727                 
728                         while (endindex > index) {
729                                 char char_to_assign;
730                                 int index_to_move;
731                                 
732                                 index_to_move = FindEditPositionFrom (endindex - 1, false);     
733                                 char_to_assign = edit_positions [index_to_move].input;
734
735                                 if (char_to_assign == char.MinValue) {
736                                         edit_positions [endindex].input = char_to_assign;
737                                 } else {
738                                         if (!edit_positions [endindex].Match (char_to_assign, out resultHint, false)) {
739                                                 testPosition = endindex;
740                                                 return false;
741                                         }
742                                 }
743                                 endindex = index_to_move;
744                         }
745                         
746                         if (endindex != InvalidIndex) {
747                                 edit_positions [endindex].Reset ();
748                         }
749                         
750                         return true;
751                 }
752                 
753                 private bool ReplaceInternal (string input, int startPosition, int endPosition, out int testPosition, out MaskedTextResultHint resultHint, bool only_test, bool dont_remove_at_end)
754                 {
755                         EditPosition [] edit_positions;
756                         resultHint = MaskedTextResultHint.Unknown;
757                         
758                         if (only_test) {
759                                 edit_positions = ClonePositions ();
760                         } else {
761                                 edit_positions = this.edit_positions;
762                         }
763                         
764                         if (input == null)
765                                 throw new ArgumentNullException ("input");
766
767                         if (endPosition >= edit_positions.Length) {
768                                 testPosition = endPosition;
769                                 resultHint = MaskedTextResultHint.PositionOutOfRange;
770                                 return false;
771                         }
772                         
773                         if (startPosition < 0) {
774                                 testPosition = startPosition;
775                                 resultHint = MaskedTextResultHint.PositionOutOfRange;
776                                 return false;
777                         }
778                         
779                         if (startPosition >= edit_positions.Length) {
780                                 testPosition = startPosition;
781                                 resultHint = MaskedTextResultHint.PositionOutOfRange;
782                                 return false;
783                         }
784                         
785                         if (startPosition > endPosition) {
786                                 testPosition = startPosition;
787                                 resultHint = MaskedTextResultHint.PositionOutOfRange;
788                                 return false;
789                         }
790
791                         
792                         if (input.Length == 0) {
793                                 return RemoveAtInternal (startPosition, endPosition, out testPosition, out resultHint, only_test);
794                         }
795
796                         int previous_position = startPosition;
797                         int current_position = previous_position;
798                         MaskedTextResultHint tmpResult = MaskedTextResultHint.Unknown;
799                         testPosition = InvalidIndex;
800                         
801                         for (int i = 0; i < input.Length; i++) {
802                                 char current_input = input [i];
803                                 
804                                 current_position = previous_position;
805
806                                 if (VerifyEscapeChar (current_input, current_position)) {
807                                         if (edit_positions [current_position].FilledIn &&
808                                                 edit_positions [current_position].Editable &&
809                                                 (current_input == ' ' && ResetOnSpace) || (current_input == PromptChar && ResetOnPrompt)) {
810                                                 edit_positions [current_position].Reset ();
811                                                 tmpResult = MaskedTextResultHint.SideEffect;
812                                         } else {
813                                                 tmpResult = MaskedTextResultHint.CharacterEscaped;
814                                         }
815                                 } else if (current_position < edit_positions.Length && !edit_positions [current_position].Editable && FindAssignedEditPositionInRange (current_position, endPosition, true) == InvalidIndex) {
816                                         // If replacing over a literal, the replacement character is INSERTED at the next
817                                         // available edit position. Weird, huh?
818                                         current_position = FindEditPositionFrom (current_position, true);
819                                         if (current_position == InvalidIndex) {
820                                                 resultHint = MaskedTextResultHint.UnavailableEditPosition;
821                                                 testPosition = edit_positions.Length;
822                                                 return false;
823                                         }
824                                         if (!InsertAtInternal (current_input.ToString (), current_position, out testPosition, out tmpResult, only_test)) {
825                                                 resultHint = tmpResult;
826                                                 return false;
827                                         }
828                                 } else { 
829                                 
830                                         current_position = FindEditPositionFrom (current_position, true);
831
832                                         if (current_position == InvalidIndex) {
833                                                 testPosition = edit_positions.Length;
834                                                 resultHint = MaskedTextResultHint.UnavailableEditPosition;
835                                                 return false;
836                                         }
837
838                                         if (!IsValidInputChar (current_input)) {
839                                                 testPosition = current_position;
840                                                 resultHint = MaskedTextResultHint.InvalidInput;
841                                                 return false;
842                                         }
843
844                                         if (!ReplaceInternal (edit_positions, current_input, current_position, out testPosition, out tmpResult, false)) {
845                                                 resultHint = tmpResult;
846                                                 return false;
847                                         }
848                                 }
849                                 
850                                 if (tmpResult > resultHint) {
851                                         resultHint = tmpResult;
852                                 }
853                                 
854                                 previous_position = current_position + 1;
855                         }
856
857                         testPosition = current_position;
858                         
859                         int tmpPosition;
860                         if (!dont_remove_at_end && previous_position <= endPosition) {
861                                 if (!RemoveAtInternal (previous_position, endPosition, out tmpPosition, out tmpResult, only_test)) {
862                                         testPosition = tmpPosition;
863                                         resultHint = tmpResult;
864                                         return false;
865                                 }
866                         }
867                         if (tmpResult == MaskedTextResultHint.Success && resultHint < MaskedTextResultHint.SideEffect) {
868                                 resultHint = MaskedTextResultHint.SideEffect;
869                         }
870                                 
871                         return true;
872                 }
873                 
874                 private bool ReplaceInternal (EditPosition [] edit_positions, char input, int position, out int testPosition, out MaskedTextResultHint resultHint, bool only_test)
875                 {
876                         testPosition = position;
877
878                         if (!IsValidInputChar (input)) {
879                                 resultHint = MaskedTextResultHint.InvalidInput;
880                                 return false;
881                         }
882
883                         if (VerifyEscapeChar (input, position)) {
884                                 if (edit_positions [position].FilledIn && edit_positions [position].Editable && (input == ' ' && ResetOnSpace) || (input == PromptChar && ResetOnPrompt)) {
885                                         edit_positions [position].Reset ();
886                                         resultHint = MaskedTextResultHint.SideEffect;
887                                 } else {
888                                         resultHint = MaskedTextResultHint.CharacterEscaped;
889                                 }
890                                 testPosition = position;
891                                 return true;
892                         }
893
894                         if (!edit_positions [position].Editable) {
895                                 resultHint = MaskedTextResultHint.NonEditPosition;
896                                 return false;
897                         }
898                         
899                         bool is_filled = edit_positions [position].FilledIn;
900                         if (is_filled && edit_positions [position].input == input) {
901                                 if (VerifyEscapeChar (input, position)) {
902                                         resultHint = MaskedTextResultHint.CharacterEscaped;
903                                 } else {
904                                         resultHint = MaskedTextResultHint.NoEffect;
905                                 }
906                         } else if (input == ' ' && this.ResetOnSpace) {
907                                 if (is_filled) {
908                                         resultHint = MaskedTextResultHint.SideEffect;
909                                         edit_positions [position].Reset ();
910                                 } else {
911                                         resultHint = MaskedTextResultHint.CharacterEscaped;
912                                 }
913                                 return true;
914                         } else if (VerifyEscapeChar (input, position)) {
915                                 resultHint = MaskedTextResultHint.SideEffect;
916                         } else {
917                                 resultHint = MaskedTextResultHint.Success;
918                         }
919                         MaskedTextResultHint tmpResult;
920                         if (!edit_positions [position].Match (input, out tmpResult, false)) {
921                                 resultHint = tmpResult;
922                                 return false;
923                         }
924
925                         return true;
926                 }
927
928                 private bool RemoveAtInternal (int startPosition, int endPosition, out int testPosition, out MaskedTextResultHint resultHint, bool only_testing)
929                 {
930                         EditPosition [] edit_positions;
931                         testPosition = -1;
932                         resultHint = MaskedTextResultHint.Unknown;
933
934                         if (only_testing) {
935                                 edit_positions = ClonePositions ();
936                         } else {
937                                 edit_positions = this.edit_positions;
938                         }
939
940                         if (endPosition < 0 || endPosition >= edit_positions.Length) {
941                                 testPosition = endPosition;
942                                 resultHint = MaskedTextResultHint.PositionOutOfRange;
943                                 return false;
944                         }
945                         
946                         if (startPosition < 0 || startPosition >= edit_positions.Length) {
947                                 testPosition = startPosition;
948                                 resultHint = MaskedTextResultHint.PositionOutOfRange;
949                                 return false;
950                         }
951                         
952                         
953                         if (startPosition > endPosition) {
954                                 testPosition = startPosition;
955                                 resultHint = MaskedTextResultHint.PositionOutOfRange;
956                                 return false;
957                         }
958                         int edit_positions_in_range = 0;
959                         for (int i = startPosition; i <= endPosition; i++) {
960                                 if (edit_positions [i].Editable) {
961                                         edit_positions_in_range++;
962                                 }
963                         }
964                         
965                         if (edit_positions_in_range == 0) {
966                                 testPosition = startPosition;
967                                 resultHint = MaskedTextResultHint.NoEffect;
968                                 return true;
969                         }
970                         
971                         int current_edit_position = FindEditPositionFrom (startPosition, true);
972                         while (current_edit_position != InvalidIndex) {
973                                 // Find the edit position that will reach the current position.
974                                 int next_index = FindEditPositionFrom (current_edit_position + 1, true);
975                                 for (int j = 1; j < edit_positions_in_range && next_index != InvalidIndex; j++) {
976                                         next_index = FindEditPositionFrom (next_index + 1, true);
977                                 }
978                                 
979                                 if (next_index == InvalidIndex) {
980                                         if (edit_positions [current_edit_position].FilledIn) {
981                                                 edit_positions [current_edit_position].Reset ();
982                                                 resultHint = MaskedTextResultHint.Success;      
983                                         } else {
984                                                 if (resultHint < MaskedTextResultHint.NoEffect) {
985                                                         resultHint = MaskedTextResultHint.NoEffect;
986                                                 }
987                                         }
988                                 } else {
989                                         if (!edit_positions [next_index].FilledIn) {
990                                                 if (edit_positions [current_edit_position].FilledIn) {
991                                                         edit_positions [current_edit_position].Reset ();
992                                                         resultHint = MaskedTextResultHint.Success;      
993                                                 } else {
994                                                         if (resultHint < MaskedTextResultHint.NoEffect) {
995                                                                 resultHint = MaskedTextResultHint.NoEffect;
996                                                         }
997                                                 }
998                                         } else {
999                                                 MaskedTextResultHint tmpResult = MaskedTextResultHint.Unknown;
1000                                                 if (edit_positions [current_edit_position].FilledIn) {
1001                                                         resultHint = MaskedTextResultHint.Success;
1002                                                 } else if (resultHint < MaskedTextResultHint.SideEffect) {
1003                                                         resultHint = MaskedTextResultHint.SideEffect;
1004                                                 }
1005                                                 if (!edit_positions [current_edit_position].Match (edit_positions [next_index].input, out tmpResult, false)) {
1006                                                         resultHint = tmpResult;
1007                                                         testPosition = current_edit_position;
1008                                                         return false;
1009                                                 }
1010                                         }       
1011                                         edit_positions [next_index].Reset ();
1012                                 }
1013                                 current_edit_position = FindEditPositionFrom (current_edit_position + 1, true);
1014                         }
1015                         
1016                         if (resultHint == MaskedTextResultHint.Unknown) {
1017                                 resultHint = MaskedTextResultHint.NoEffect;
1018                         }
1019                         
1020                         testPosition = startPosition;
1021                         
1022                         return true;
1023                 }
1024                 
1025                 private bool InsertAtInternal (string str_input, int position, out int testPosition, out MaskedTextResultHint resultHint, bool only_testing)
1026                 {
1027                         EditPosition [] edit_positions;
1028                         testPosition = -1;
1029                         resultHint = MaskedTextResultHint.Unknown;
1030
1031                         if (only_testing) {
1032                                 edit_positions = ClonePositions ();
1033                         } else {
1034                                 edit_positions = this.edit_positions;
1035                         }
1036
1037                         if (position < 0 || position >= edit_positions.Length) {
1038                                 testPosition = 0;
1039                                 resultHint = MaskedTextResultHint.PositionOutOfRange;
1040                                 return false;
1041                         }
1042                         
1043                         if (!IsInsertableString (str_input, position, out testPosition, out resultHint)) {
1044                                 return false;
1045                         }
1046                         
1047                         resultHint = MaskedTextResultHint.Unknown;
1048                         
1049                         int next_position = position;
1050                         for (int i = 0; i < str_input.Length; i++) {
1051                                 char input = str_input [i];
1052                                 int index = FindEditPositionFrom (next_position, true); // Find the first edit position (here the input will go)
1053                                 int endindex = FindUnassignedEditPositionFrom (next_position, true); // Find the first non-assigned edit position
1054                                 bool escaped = false;
1055
1056                                 if (VerifyEscapeChar (input, next_position)) {
1057                                         escaped = true;
1058
1059                                         if (input.ToString () == edit_positions [next_position].Text) {
1060                                                 if (FindAssignedEditPositionInRange (0, next_position - 1, true) != InvalidIndex && endindex == InvalidIndex) {
1061                                                         resultHint = MaskedTextResultHint.UnavailableEditPosition;
1062                                                         testPosition = edit_positions.Length;
1063                                                         return false;
1064                                                 }
1065                                                 resultHint = MaskedTextResultHint.CharacterEscaped;
1066                                                 testPosition = next_position;
1067                                                 next_position++;
1068                                                 continue;                                               
1069                                         }
1070                                 }
1071                                 
1072                                 if (!escaped && index == InvalidIndex) {
1073                                         // No edit positions left at all in the string
1074                                         testPosition = edit_positions.Length;
1075                                         resultHint = MaskedTextResultHint.UnavailableEditPosition;
1076                                         return false;
1077                                 }
1078                                 
1079                                 if (index == InvalidIndex) {
1080                                         index = next_position;
1081                                 }
1082                                 
1083                                 bool was_filled = edit_positions [index].FilledIn;
1084                                 bool shift = was_filled;
1085                                 if (shift) {
1086                                         if (!ShiftPositionsRight (edit_positions, index, out testPosition, out resultHint)) {
1087                                                 return false;
1088                                         }
1089                                 }
1090
1091                                 testPosition = index;
1092                                 if (escaped) {
1093                                         if (was_filled) {
1094                                                 resultHint = MaskedTextResultHint.Success;
1095                                         } else if (!edit_positions [index].Editable && input.ToString () == edit_positions [index].Text) {
1096                                                 resultHint = MaskedTextResultHint.CharacterEscaped;
1097                                                 testPosition = next_position;
1098                                         } else {
1099                                                 int first_edit_position = FindEditPositionFrom (index, true);
1100                                                 if (first_edit_position == InvalidIndex) {
1101                                                         resultHint = MaskedTextResultHint.UnavailableEditPosition;
1102                                                         testPosition = edit_positions.Length;
1103                                                         return false;
1104                                                 }
1105                         
1106                                                 resultHint = MaskedTextResultHint.CharacterEscaped;
1107                                                 if (input.ToString () == edit_positions [next_position].Text) {
1108                                                         testPosition = next_position;
1109                                                 }
1110                                         }
1111                                 } else {
1112                                         MaskedTextResultHint tmpResult;
1113                                         if (!edit_positions [index].Match (input, out tmpResult, false)) {
1114                                                 resultHint = tmpResult;
1115                                                 return false;
1116                                         }
1117                                         if (resultHint < tmpResult) {
1118                                                 resultHint = tmpResult;
1119                                         }
1120                                 }
1121                                 next_position = index + 1;
1122                                 
1123                         }
1124                         
1125                         return true;
1126                 }
1127 #endregion
1128                 #region Public constructors
1129                 static MaskedTextProvider()
1130                 {
1131                 }
1132
1133                 public MaskedTextProvider(string mask) 
1134                         : this (mask, null, true, default_prompt_char, default_password_char, false)
1135                 {
1136                 }
1137
1138                 public MaskedTextProvider (string mask, bool restrictToAscii) 
1139                         : this (mask, null, true, default_prompt_char, default_password_char, restrictToAscii)
1140                 {
1141                 }
1142
1143                 public MaskedTextProvider (string mask, CultureInfo culture) 
1144                         : this (mask, culture, true, default_prompt_char, default_password_char, false)
1145                 {
1146                 }
1147
1148                 public MaskedTextProvider (string mask, char passwordChar, bool allowPromptAsInput) 
1149                         : this (mask, null, allowPromptAsInput, default_prompt_char, passwordChar, false)
1150                 {
1151                 }
1152
1153                 public MaskedTextProvider (string mask, CultureInfo culture, bool restrictToAscii) 
1154                         : this (mask, culture, true, default_prompt_char, default_password_char, restrictToAscii)
1155                 {
1156                 }
1157                 
1158                 public MaskedTextProvider(string mask, CultureInfo culture, char passwordChar, bool allowPromptAsInput) 
1159                         : this (mask, culture, allowPromptAsInput, default_prompt_char, passwordChar, false)
1160                 {
1161                 }
1162                 
1163                 public MaskedTextProvider(string mask, CultureInfo culture, bool allowPromptAsInput, char promptChar, char passwordChar, bool restrictToAscii)
1164                 {
1165                         SetMask (mask);
1166                         
1167                         if (culture == null)
1168                                 this.culture = Threading.Thread.CurrentThread.CurrentCulture;
1169                         else
1170                                 this.culture = culture;
1171                                 
1172                         this.allow_prompt_as_input = allowPromptAsInput;
1173                         
1174                         this.PromptChar = promptChar;
1175                         this.PasswordChar = passwordChar;
1176                         this.ascii_only = restrictToAscii;
1177                         
1178                         include_literals = true;
1179                         reset_on_prompt = true;
1180                         reset_on_space = true;
1181                         skip_literals = true;
1182                 }
1183 #endregion
1184
1185 #region Public methods
1186                 public bool Add(char input)
1187                 {
1188                         int testPosition;
1189                         MaskedTextResultHint resultHint;
1190                         return Add (input, out testPosition, out resultHint);
1191                 }
1192
1193                 public bool Add (string input)
1194                 {
1195                         int testPosition;
1196                         MaskedTextResultHint resultHint;
1197                         return Add (input, out testPosition, out resultHint);
1198                 }
1199
1200                 public bool Add (char input, out int testPosition, out MaskedTextResultHint resultHint)
1201                 {
1202                         return AddInternal (input, out testPosition, out resultHint, true, false);
1203                 }
1204
1205                 public bool Add (string input, out int testPosition, out MaskedTextResultHint resultHint)
1206                 {
1207                         bool result;
1208                         
1209                         result = AddInternal (input, out testPosition, out resultHint, true);
1210                         
1211                         if (result) {
1212                                 result = AddInternal (input, out testPosition, out resultHint, false);
1213                         }
1214                         
1215                         return result;
1216                 }
1217
1218                 public void Clear ()
1219                 {
1220                         MaskedTextResultHint resultHint;
1221                         Clear (out resultHint);
1222                 }
1223
1224                 public void Clear (out MaskedTextResultHint resultHint)
1225                 {
1226                         resultHint = MaskedTextResultHint.NoEffect;
1227                         for (int i = 0; i < edit_positions.Length; i++) {
1228                                 if (edit_positions [i].Editable && edit_positions [i].FilledIn) {
1229                                         edit_positions [i].Reset ();
1230                                         resultHint = MaskedTextResultHint.Success;
1231                                 }
1232                         }
1233                 }
1234
1235                 public object Clone ()
1236                 {
1237                         MaskedTextProvider result = new MaskedTextProvider (mask);
1238                         
1239                         result.allow_prompt_as_input = allow_prompt_as_input;
1240                         result.ascii_only = ascii_only;
1241                         result.culture = culture;
1242                         result.edit_positions = ClonePositions ();
1243                         result.include_literals = include_literals;
1244                         result.include_prompt = include_prompt;
1245                         result.is_password = is_password;
1246                         result.mask = mask;
1247                         result.password_char = password_char;
1248                         result.prompt_char = prompt_char;
1249                         result.reset_on_prompt = reset_on_prompt;
1250                         result.reset_on_space = reset_on_space;
1251                         result.skip_literals = skip_literals;
1252                                                 
1253                         return result;
1254                 }
1255
1256                 public int FindAssignedEditPositionFrom (int position, bool direction)
1257                 {
1258                         if (direction) {
1259                                 return FindAssignedEditPositionInRange (position, edit_positions.Length - 1, direction);
1260                         } else {
1261                                 return FindAssignedEditPositionInRange (0, position, direction);                        
1262                         }
1263                 }
1264
1265                 public int FindAssignedEditPositionInRange (int startPosition, int endPosition, bool direction)
1266                 {
1267                         int step;
1268                         int start, end;
1269                         
1270                         if (startPosition < 0)
1271                                 startPosition = 0;
1272                         if (endPosition >= edit_positions.Length)
1273                                 endPosition = edit_positions.Length - 1;
1274
1275                         if (startPosition > endPosition)
1276                                 return InvalidIndex;
1277                                 
1278                         step = direction ? 1 : -1;
1279                         start = direction ? startPosition : endPosition;
1280                         end = (direction ? endPosition: startPosition) + step;
1281
1282                         for (int i = start; i != end; i+=step) {
1283                                 if (edit_positions [i].Editable && edit_positions [i].FilledIn)
1284                                         return i;
1285                         }
1286                         return InvalidIndex;
1287                 }
1288
1289                 public int FindEditPositionFrom (int position, bool direction)
1290                 {
1291                         if (direction) {
1292                                 return FindEditPositionInRange (position, edit_positions.Length - 1, direction);
1293                         } else {
1294                                 return FindEditPositionInRange (0, position, direction);
1295                         }
1296                 }
1297
1298                 public int FindEditPositionInRange (int startPosition, int endPosition, bool direction)
1299                 {
1300                         int step;
1301                         int start, end;
1302
1303                         if (startPosition < 0)
1304                                 startPosition = 0;
1305                         if (endPosition >= edit_positions.Length)
1306                                 endPosition = edit_positions.Length - 1;
1307
1308                         if (startPosition > endPosition)
1309                                 return InvalidIndex;
1310
1311                         step = direction ? 1 : -1;
1312                         start = direction ? startPosition : endPosition;
1313                         end = (direction ? endPosition : startPosition) + step;
1314
1315                         for (int i = start; i != end; i += step) {
1316                                 if (edit_positions [i].Editable)
1317                                         return i;
1318                         }
1319                         return InvalidIndex;
1320                 }
1321
1322                 public int FindNonEditPositionFrom (int position, bool direction)
1323                 {
1324                         if (direction) {
1325                                 return FindNonEditPositionInRange (position, edit_positions.Length - 1, direction);
1326                         } else {
1327                                 return FindNonEditPositionInRange (0, position, direction);
1328                         }
1329                 }
1330
1331                 public int FindNonEditPositionInRange (int startPosition, int endPosition, bool direction)
1332                 {               
1333                         int step;
1334                         int start, end;
1335
1336                         if (startPosition < 0)
1337                                 startPosition = 0;
1338                         if (endPosition >= edit_positions.Length)
1339                                 endPosition = edit_positions.Length - 1;
1340
1341                         if (startPosition > endPosition)
1342                                 return InvalidIndex;
1343
1344                         step = direction ? 1 : -1;
1345                         start = direction ? startPosition : endPosition;
1346                         end = (direction ? endPosition : startPosition) + step;
1347
1348                         for (int i = start; i != end; i += step) {
1349                                 if (!edit_positions [i].Editable)
1350                                         return i;
1351                         }
1352                         return InvalidIndex;
1353                 }
1354
1355                 public int FindUnassignedEditPositionFrom (int position, bool direction)
1356                 {
1357                         if (direction) {
1358                                 return FindUnassignedEditPositionInRange (position, edit_positions.Length - 1, direction);
1359                         } else {
1360                                 return FindUnassignedEditPositionInRange (0, position, direction);
1361                         }
1362                 }
1363
1364                 public int FindUnassignedEditPositionInRange (int startPosition, int endPosition, bool direction)
1365                 {
1366                         int step;
1367                         int start, end;
1368
1369                         if (startPosition < 0)
1370                                 startPosition = 0;
1371                         if (endPosition >= edit_positions.Length)
1372                                 endPosition = edit_positions.Length - 1;
1373
1374                         if (startPosition > endPosition)
1375                                 return InvalidIndex;
1376
1377                         step = direction ? 1 : -1;
1378                         start = direction ? startPosition : endPosition;
1379                         end = (direction ? endPosition : startPosition) + step;
1380
1381                         for (int i = start; i != end; i += step) {
1382                                 if (edit_positions [i].Editable && !edit_positions [i].FilledIn)
1383                                         return i;
1384                         }
1385                         return InvalidIndex;
1386                 }
1387
1388                 public static bool GetOperationResultFromHint (MaskedTextResultHint hint)
1389                 {
1390                         return (hint == MaskedTextResultHint.CharacterEscaped || 
1391                                 hint == MaskedTextResultHint.NoEffect || 
1392                                 hint == MaskedTextResultHint.SideEffect || 
1393                                 hint == MaskedTextResultHint.Success);
1394                 }
1395
1396                 public bool InsertAt (char input, int position)
1397                 {
1398                         int testPosition;
1399                         MaskedTextResultHint resultHint;
1400                         return InsertAt (input, position, out testPosition, out resultHint);
1401                 }
1402
1403                 public bool InsertAt (string input, int position)
1404                 {
1405                         int testPosition;
1406                         MaskedTextResultHint resultHint;
1407                         return InsertAt (input, position, out testPosition, out resultHint);
1408                 }
1409
1410                 public bool InsertAt (char input, int position, out int testPosition, out MaskedTextResultHint resultHint)
1411                 {
1412                         return InsertAt (input.ToString (), position, out testPosition, out resultHint);                
1413                 }
1414
1415                 public bool InsertAt (string input, int position, out int testPosition, out MaskedTextResultHint resultHint)
1416                 {
1417                         if (input == null)
1418                                 throw new ArgumentNullException ("input");
1419                         
1420                         if (position >= edit_positions.Length) {
1421                                 testPosition = position;
1422                                 resultHint = MaskedTextResultHint.PositionOutOfRange;
1423                                 return false;
1424                         }
1425                         
1426                         if (input == string.Empty) {
1427                                 testPosition = position;
1428                                 resultHint = MaskedTextResultHint.NoEffect;
1429                                 return true;
1430                         }
1431                         
1432                         bool result;
1433
1434                         result = InsertAtInternal (input, position, out testPosition, out resultHint, true);
1435
1436                         if (result) {   
1437                                 result = InsertAtInternal (input, position, out testPosition, out resultHint, false);
1438                         }
1439
1440                         return result;  
1441                 }
1442
1443                 public bool IsAvailablePosition (int position)
1444                 {
1445                         if (position < 0 || position >= edit_positions.Length)
1446                                 return false;
1447                                 
1448                         return edit_positions [position].Editable && !edit_positions [position].FilledIn;
1449                 }
1450
1451                 public bool IsEditPosition (int position)
1452                 {
1453                         if (position < 0 || position >= edit_positions.Length)
1454                                 return false;
1455                                 
1456                         return edit_positions [position].Editable;
1457                 }
1458
1459                 public static bool IsValidInputChar (char c)
1460                 {
1461                         return char.IsLetterOrDigit (c) || char.IsPunctuation (c) || char.IsSymbol (c) || c == ' ';
1462                 }
1463
1464                 public static bool IsValidMaskChar (char c)
1465                 {
1466                         return char.IsLetterOrDigit (c) || char.IsPunctuation (c) || char.IsSymbol (c) || c == ' ';
1467                 }
1468
1469                 public static bool IsValidPasswordChar (char c)
1470                 {
1471                         return char.IsLetterOrDigit (c) || char.IsPunctuation (c) || char.IsSymbol (c) || c == ' ' || c == char.MinValue;
1472                 }
1473
1474                 public bool Remove ()
1475                 {
1476                         int testPosition;
1477                         MaskedTextResultHint resultHint;
1478                         return Remove (out testPosition, out resultHint);
1479                 }
1480
1481                 public bool Remove (out int testPosition, out MaskedTextResultHint resultHint)
1482                 {
1483                         if (LastAssignedPosition == InvalidIndex) {
1484                                 resultHint = MaskedTextResultHint.NoEffect;
1485                                 testPosition = 0;
1486                                 return true;
1487                         }
1488                         
1489                         testPosition = LastAssignedPosition;
1490                         resultHint = MaskedTextResultHint.Success;
1491                         edit_positions [LastAssignedPosition].input = char.MinValue;
1492                         
1493                         return true;
1494                 }
1495
1496                 public bool RemoveAt (int position)
1497                 {
1498                         return RemoveAt (position, position);
1499                 }
1500
1501                 public bool RemoveAt (int startPosition, int endPosition)
1502                 {
1503                         int testPosition;
1504                         MaskedTextResultHint resultHint;
1505                         return RemoveAt (startPosition, endPosition, out testPosition, out resultHint);
1506                 }
1507
1508                 public bool RemoveAt (int startPosition, int endPosition, out int testPosition, out MaskedTextResultHint resultHint)
1509                 {       
1510                         bool result;
1511                         
1512                         result = RemoveAtInternal (startPosition, endPosition, out testPosition, out resultHint, true);
1513                         
1514                         if (result) {
1515                                 result = RemoveAtInternal (startPosition, endPosition, out testPosition, out resultHint, false);
1516                         }
1517                 
1518                         return result;
1519                 }
1520
1521                 public bool Replace (char input, int position)
1522                 {
1523                         int testPosition;
1524                         MaskedTextResultHint resultHint;
1525                         return Replace (input, position, out testPosition, out resultHint);
1526                 }
1527
1528                 public bool Replace (string input, int position)
1529                 {
1530                         int testPosition;
1531                         MaskedTextResultHint resultHint;
1532                         return Replace (input, position, out testPosition, out resultHint);
1533                 }
1534
1535                 public bool Replace (char input, int position, out int testPosition, out MaskedTextResultHint resultHint)
1536                 {
1537                         if (position < 0 || position >= edit_positions.Length) {
1538                                 testPosition = position;
1539                                 resultHint = MaskedTextResultHint.PositionOutOfRange;
1540                                 return false;
1541                         }
1542
1543                         if (VerifyEscapeChar (input, position)) {
1544                                 if (edit_positions [position].FilledIn && edit_positions [position].Editable && (input == ' ' && ResetOnSpace) || (input == PromptChar && ResetOnPrompt)) {
1545                                         edit_positions [position].Reset ();
1546                                         resultHint = MaskedTextResultHint.SideEffect;
1547                                 } else {
1548                                         resultHint = MaskedTextResultHint.CharacterEscaped;
1549                                 }
1550                                 testPosition = position;
1551                                 return true;
1552                         }
1553                         
1554                         int current_edit_position;
1555                         current_edit_position = FindEditPositionFrom (position, true);
1556
1557                         if (current_edit_position == InvalidIndex) {
1558                                 testPosition = position;
1559                                 resultHint = MaskedTextResultHint.UnavailableEditPosition;
1560                                 return false;
1561                         }
1562
1563                         if (!IsValidInputChar (input)) {
1564                                 testPosition = current_edit_position;
1565                                 resultHint = MaskedTextResultHint.InvalidInput;
1566                                 return false;
1567                         }
1568                         
1569                         return ReplaceInternal (edit_positions, input, current_edit_position, out testPosition, out resultHint, false);
1570                 }
1571
1572                 public bool Replace (string input, int position, out int testPosition, out MaskedTextResultHint resultHint)
1573                 {
1574                         if (input == null)
1575                                 throw new ArgumentNullException ("input");
1576
1577                         if (position < 0 || position >= edit_positions.Length) {
1578                                 testPosition = position;
1579                                 resultHint = MaskedTextResultHint.PositionOutOfRange;
1580                                 return false;
1581                         }
1582                         
1583                         if (input.Length == 0) {
1584                                 return RemoveAt (position, position, out testPosition, out resultHint);
1585                         }
1586                         
1587                         bool result;
1588                         
1589                         result = ReplaceInternal (input, position, edit_positions.Length - 1, out testPosition, out resultHint, true, true);
1590                         
1591                         if (result) {
1592                                 result = ReplaceInternal (input, position, edit_positions.Length - 1, out testPosition, out resultHint, false, true);   
1593                         }
1594
1595                         return result;
1596                 }
1597
1598                 public bool Replace (char input, int startPosition, int endPosition, out int testPosition, out MaskedTextResultHint resultHint)
1599                 {
1600
1601                         if (endPosition >= edit_positions.Length) {
1602                                 testPosition = endPosition;
1603                                 resultHint = MaskedTextResultHint.PositionOutOfRange;
1604                                 return false;
1605                         }
1606
1607                         if (startPosition < 0) {
1608                                 testPosition = startPosition;
1609                                 resultHint = MaskedTextResultHint.PositionOutOfRange;
1610                                 return false;
1611                         }
1612                         
1613                         if (startPosition > endPosition) {
1614                                 testPosition = startPosition;
1615                                 resultHint = MaskedTextResultHint.PositionOutOfRange;
1616                                 return false;
1617                         }
1618                         
1619                         if (startPosition == endPosition) {
1620                                 return ReplaceInternal (edit_positions, input, startPosition, out testPosition, out resultHint, false);
1621                         }
1622
1623                         return Replace (input.ToString (), startPosition, endPosition, out testPosition, out resultHint);
1624                 }
1625
1626                 public bool Replace (string input, int startPosition, int endPosition, out int testPosition, out MaskedTextResultHint resultHint)
1627                 {
1628                         bool result;
1629                         
1630                         result = ReplaceInternal (input, startPosition, endPosition, out testPosition, out resultHint, true, false);
1631                         
1632                         if (result) {
1633                                 result = ReplaceInternal (input, startPosition, endPosition, out testPosition, out resultHint, false, false);
1634                         }
1635                         return result;
1636                 }
1637
1638                 public bool Set (string input)
1639                 {
1640                         int testPosition;
1641                         MaskedTextResultHint resultHint;
1642                         return Set (input, out testPosition, out resultHint);
1643                 }
1644
1645                 public bool Set (string input, out int testPosition, out MaskedTextResultHint resultHint)
1646                 {
1647                         bool result;
1648
1649                         if (input == null) {
1650                                 throw new ArgumentNullException ("input");
1651                         }
1652
1653                         result = VerifyStringInternal (input, out testPosition, out resultHint, 0, true);
1654                         
1655                         if (result) {
1656                                 result = VerifyStringInternal (input, out testPosition, out resultHint, 0, false);
1657                         }
1658                         
1659                         return result;
1660                 }
1661
1662                 public string ToDisplayString ()
1663                 {
1664                         return ToString (false, true, true, 0, Length);
1665                 }
1666
1667                 public override string ToString ()
1668                 {
1669                         return ToString (true, IncludePrompt, IncludeLiterals, 0, Length);
1670                 }
1671
1672                 public string ToString (bool ignorePasswordChar)
1673                 {
1674                         return ToString (ignorePasswordChar, IncludePrompt, IncludeLiterals, 0, Length);
1675                 }
1676
1677                 public string ToString (bool includePrompt, bool includeLiterals)
1678                 {
1679                         return ToString (true, includePrompt, includeLiterals, 0, Length);
1680                 }
1681
1682                 public string ToString (int startPosition, int length)
1683                 {
1684                         return ToString (true, IncludePrompt, IncludeLiterals, startPosition, length);
1685                 }
1686
1687                 public string ToString (bool ignorePasswordChar, int startPosition, int length)
1688                 {
1689                         return ToString (ignorePasswordChar, IncludePrompt, IncludeLiterals, startPosition, length);
1690                 }
1691
1692                 public string ToString (bool includePrompt, bool includeLiterals, int startPosition, int length)
1693                 {
1694                         return ToString (true, includePrompt, includeLiterals, startPosition, length);
1695                 }
1696
1697                 public string ToString (bool ignorePasswordChar, bool includePrompt, bool includeLiterals, int startPosition, int length)
1698                 {
1699                         if (startPosition < 0)
1700                                 startPosition = 0;
1701                         
1702                         if (length <= 0)
1703                                 return string.Empty;
1704                         
1705                         StringBuilder result = new StringBuilder ();
1706                         int start = startPosition;
1707                         int end = startPosition + length - 1;
1708                         
1709                         if (end >= edit_positions.Length) {
1710                                 end = edit_positions.Length - 1;
1711                         }
1712                         int last_assigned_position = FindAssignedEditPositionInRange (start, end, false);
1713                         
1714                         // Find the last position in the mask to check for
1715                         if (!includePrompt) {
1716                                 int last_non_edit_position;
1717                                 
1718                                 last_non_edit_position = FindNonEditPositionInRange (start, end, false);
1719                                 
1720                                 if (includeLiterals) {
1721                                         end = last_assigned_position > last_non_edit_position ? last_assigned_position : last_non_edit_position;
1722                                 } else {
1723                                         end = last_assigned_position;
1724                                 }
1725                         }
1726                         
1727                         for (int i = start; i <= end; i++) {
1728                                 EditPosition ed = edit_positions [i];
1729                                 
1730                                 if (ed.Type == EditType.Literal) {
1731                                         if (includeLiterals) {
1732                                                 result.Append (ed.Text);
1733                                         }                               
1734                                 } else if (ed.Editable) {
1735                                         if (IsPassword) {
1736                                                 if (!ed.FilledIn) { // Nothing to hide or show.
1737                                                         if (includePrompt)
1738                                                                 result.Append (PromptChar);
1739                                                         else
1740                                                                 result.Append (" ");
1741                                                 } else if (ignorePasswordChar)
1742                                                         result.Append (ed.Input);
1743                                                 else
1744                                                         result.Append (PasswordChar);
1745                                         } else if (!ed.FilledIn) {
1746                                                 if (includePrompt) {
1747                                                         result.Append (PromptChar);
1748                                                 } else if (includeLiterals) {
1749                                                         result.Append (" ");
1750                                                 } else if (last_assigned_position != InvalidIndex && last_assigned_position > i) {
1751                                                         result.Append (" ");
1752                                                 }
1753                                         } else {
1754                                                 result.Append (ed.Text);
1755                                         }
1756                                 } else {
1757                                         if (includeLiterals)
1758                                                 result.Append (ed.Text);
1759                                 }
1760                         }
1761                         
1762                         return result.ToString ();
1763                 }
1764
1765                 public bool VerifyChar (char input, int position, out MaskedTextResultHint hint)
1766                 {
1767                         return VerifyCharInternal (input, position, out hint, true);
1768                 }
1769
1770                 public bool VerifyEscapeChar (char input, int position)
1771                 {
1772                         if (position >= edit_positions.Length || position < 0) {
1773                                 return false;
1774                         }
1775                         
1776                         if (!edit_positions [position].Editable) {
1777                                 if (SkipLiterals) {
1778                                         return input.ToString () == edit_positions [position].Text;
1779                                 } else {
1780                                         return false;
1781                                 }
1782                         }
1783                         
1784                         if (ResetOnSpace && input == ' ') {
1785                                 return true;
1786                         } else if (ResetOnPrompt && input == PromptChar) {
1787                                 return true;
1788                         } else {
1789                                 return false;
1790                         }
1791                 }
1792
1793                 public bool VerifyString (string input)
1794                 {
1795                         int testPosition;
1796                         MaskedTextResultHint resultHint;
1797                         return VerifyString (input, out testPosition, out resultHint);
1798                 }
1799
1800                 public bool VerifyString (string input, out int testPosition, out MaskedTextResultHint resultHint)
1801                 {
1802                         if (input == null || input.Length == 0) {                       
1803                                 testPosition = 0;
1804                                 resultHint = MaskedTextResultHint.NoEffect;
1805                                 return true;
1806                         }
1807                         
1808                         return VerifyStringInternal (input, out testPosition, out resultHint, 0, true);
1809                 }
1810 #endregion
1811
1812 #region Public properties
1813                 public bool AllowPromptAsInput {
1814                         get {
1815                                 return allow_prompt_as_input;
1816                         }
1817                 }
1818                 
1819                 public bool AsciiOnly {
1820                         get {
1821                                 return ascii_only;
1822                         }
1823                 }
1824                 
1825                 public int AssignedEditPositionCount {
1826                         get {
1827                                 int result = 0;
1828                                 for (int i = 0; i < edit_positions.Length; i++) {
1829                                         if (edit_positions [i].FilledIn) {
1830                                                 result++;
1831                                         }
1832                                 }
1833                                 return result;
1834                         }
1835                 }
1836                 
1837                 public int AvailableEditPositionCount {
1838                         get {
1839                                 int result = 0;
1840                                 foreach (EditPosition edit in edit_positions) {
1841                                         if (!edit.FilledIn && edit.Editable) {
1842                                                 result++;
1843                                         }
1844                                 }
1845                                 return result;
1846                         }
1847                 }
1848                 
1849                 public CultureInfo Culture {
1850                         get {
1851                                 return culture;
1852                         }
1853                 }
1854                 
1855                 public static char DefaultPasswordChar {
1856                         get {
1857                                 return '*';
1858                         }
1859                 }
1860                 
1861                 public int EditPositionCount {
1862                         get {
1863                                 int result = 0;
1864                                 foreach (EditPosition edit in edit_positions) {
1865                                         if (edit.Editable) {
1866                                                 result++;
1867                                         }
1868                                 }
1869                                                 
1870                                 return result;
1871                         }
1872                 }
1873                 
1874                 public IEnumerator EditPositions {
1875                         get {
1876                                 System.Collections.Generic.List <int> result = new System.Collections.Generic.List<int> ();
1877                                 for (int i = 0; i < edit_positions.Length; i++) {
1878                                         if (edit_positions [i].Editable) {
1879                                                 result.Add (i);
1880                                         }
1881                                 }
1882                                 return result.GetEnumerator ();
1883                         }
1884                 }
1885                 
1886                 public bool IncludeLiterals {
1887                         get {
1888                                 return include_literals;
1889                         }
1890                         set {
1891                                 include_literals = value;
1892                         }
1893                 }
1894                 
1895                 public bool IncludePrompt {
1896                         get {
1897                                 return include_prompt;
1898                         }
1899                         set {
1900                                 include_prompt = value;
1901                         }
1902                 }
1903                 
1904                 public static int InvalidIndex {
1905                         get {
1906                                 return -1;
1907                         }
1908                 }
1909                 
1910                 public bool IsPassword {
1911                         get {
1912                                 return password_char != char.MinValue;
1913                         }
1914                         set {
1915                                 password_char = value ? DefaultPasswordChar : char.MinValue;
1916                         }
1917                 }
1918                 
1919                 public char this [int index] {
1920                         get {
1921                                 if (index < 0 || index >= Length) {
1922                                         throw new IndexOutOfRangeException (index.ToString ());
1923                                 }
1924                                 
1925                                 return ToString (true, true, true, 0, edit_positions.Length) [index];
1926                         }
1927                 }
1928                 
1929                 public int LastAssignedPosition {
1930                         get {
1931                                 return FindAssignedEditPositionFrom (edit_positions.Length - 1, false);
1932                         }
1933                 }
1934                 
1935                 public int Length {
1936                         get {
1937                                 int result = 0;
1938                                 for (int i = 0; i < edit_positions.Length; i++) {
1939                                         if (edit_positions [i].Visible)
1940                                                 result++;
1941                                 }
1942                                 return result;
1943                         }
1944                 }
1945                 
1946                 public string Mask {
1947                         get {
1948                                 return mask;
1949                         }
1950                 }
1951                 
1952                 public bool MaskCompleted {
1953                         get {
1954                                 for (int i = 0; i < edit_positions.Length; i++)
1955                                         if (edit_positions [i].Required && !edit_positions [i].FilledIn)
1956                                                 return false;
1957                                 return true;
1958                         }
1959                 }
1960                 
1961                 public bool MaskFull {
1962                         get {
1963                                 for (int i = 0; i < edit_positions.Length; i++)
1964                                         if (edit_positions [i].Editable && !edit_positions [i].FilledIn)
1965                                                 return false;
1966                                 return true;
1967                         }
1968                 }
1969                 
1970                 public char PasswordChar {
1971                         get {
1972                                 return password_char;
1973                         }
1974                         set {
1975                                 password_char = value;
1976                         }
1977                 }
1978                 
1979                 public char PromptChar {
1980                         get {
1981                                 return prompt_char;
1982                         }
1983                         set {
1984                                 prompt_char = value;
1985                         }
1986                 }
1987                 
1988                 public bool ResetOnPrompt {
1989                         get {
1990                                 return reset_on_prompt;
1991                         }
1992                         set {
1993                                 reset_on_prompt = value;
1994                         }
1995                 }
1996                 
1997                 public bool ResetOnSpace {
1998                         get {
1999                                 return reset_on_space;
2000                         }
2001                         set {
2002                                 reset_on_space = value;
2003                         }
2004                 }
2005                 
2006                 public bool SkipLiterals {
2007                         get {
2008                                 return skip_literals;
2009                         }
2010                         set {
2011                                 skip_literals = value;
2012                         }
2013                 }
2014 #endregion
2015
2016         }
2017 }