1 #---------------------------------------------------------------------------- 
   3 # Authors:      Jeff Childers, Will Sadkin 
   4 # Email:        jchilders_98@yahoo.com, wsadkin@nameconnector.com 
   6 # Copyright:    (c) 2003 by Jeff Childers, Will Sadkin, 2003 
   7 # Portions:     (c) 2002 by Will Sadkin, 2002-2003 
   9 # License:      wxWindows license 
  10 #---------------------------------------------------------------------------- 
  12 #   MaskedEdit controls are based on a suggestion made on [wxPython-Users] by 
  13 #   Jason Hihn, and borrows liberally from Will Sadkin's original masked edit 
  14 #   control for time entry, TimeCtrl (which is now rewritten using this 
  17 #   MaskedEdit controls do not normally use validators, because they do 
  18 #   careful manipulation of the cursor in the text window on each keystroke, 
  19 #   and validation is cursor-position specific, so the control intercepts the 
  20 #   key codes before the validator would fire.  However, validators can be 
  21 #   provided to do data transfer to the controls. 
  23 #---------------------------------------------------------------------------- 
  25 # This file now contains the bulk of the logic behind all masked controls, 
  26 # the MaskedEditMixin class, the Field class, and the autoformat codes. 
  28 #---------------------------------------------------------------------------- 
  30 # 03/30/2004 - Will Sadkin (wsadkin@nameconnector.com) 
  32 # o Split out TextCtrl, ComboBox and IpAddrCtrl into their own files, 
  33 # o Reorganized code into masked package 
  35 # 12/09/2003 - Jeff Grimmett (grimmtooth@softhome.net) 
  37 # o Updated for wx namespace. No guarantees. This is one huge file. 
  39 # 12/13/2003 - Jeff Grimmett (grimmtooth@softhome.net) 
  41 # o Missed wx.DateTime stuff earlier. 
  43 # 12/20/2003 - Jeff Grimmett (grimmtooth@softhome.net) 
  45 # o MaskedEditMixin -> MaskedEditMixin 
  46 # o wxMaskedTextCtrl -> maskedTextCtrl 
  47 # o wxMaskedComboBoxSelectEvent -> MaskedComboBoxSelectEvent 
  48 # o wxMaskedComboBox -> MaskedComboBox 
  49 # o wxIpAddrCtrl -> IpAddrCtrl 
  50 # o wxTimeCtrl -> TimeCtrl 
  59     is a sublassed text control that can carefully control the user's input 
  60     based on a mask string you provide. 
  62     General usage example:: 
  64         control = masked.TextCtrl( win, -1, '', mask = '(###) ###-####') 
  66     The example above will create a text control that allows only numbers to be 
  67     entered and then only in the positions indicated in the mask by the # sign. 
  70     is a similar subclass of wxComboBox that allows the same sort of masking, 
  71     but also can do auto-complete of values, and can require the value typed 
  72     to be in the list of choices to be colored appropriately. 
  75     is actually a factory function for several types of masked edit controls: 
  77     =================   ================================================== 
  78     masked.TextCtrl     standard masked edit text box 
  79     masked.ComboBox     adds combobox capabilities 
  80     masked.IpAddrCtrl   adds special semantics for IP address entry 
  81     masked.TimeCtrl     special subclass handling lots of types as values 
  82     masked.NumCtrl      special subclass handling numeric values 
  83     =================   ================================================== 
  85     It works by looking for a *controlType* parameter in the keyword 
  86     arguments of the control, to determine what kind of instance to return. 
  87     If not specified as a keyword argument, the default control type returned 
  88     will be masked.TextCtrl. 
  90     Each of the above classes has its own set of arguments, but masked.Ctrl 
  91     provides a single "unified" interface for masked controls.  Those for 
  92     masked.TextCtrl, masked.ComboBox and masked.IpAddrCtrl are all documented 
  93     below; the others have their own demo pages and interface descriptions. 
  94     (See end of following discussion for how to configure the wx.MaskedCtrl() 
  95     to select the above control types.) 
  97 ========================= 
  99 Initialization Parameters 
 100 ------------------------- 
 102     Allowed mask characters and function: 
 104     =========  ========================================================== 
 106     =========  ========================================================== 
 107         #       Allow numeric only (0-9) 
 108         N       Allow letters and numbers (0-9) 
 109         A       Allow uppercase letters only 
 110         a       Allow lowercase letters only 
 111         C       Allow any letter, upper or lower 
 112         X       Allow string.letters, string.punctuation, string.digits 
 113         &       Allow string.punctuation only 
 114         \*       Allow any ansi character 
 115     =========  ========================================================== 
 118     These controls define these sets of characters using string.letters, 
 119     string.uppercase, etc.  These sets are affected by the system locale 
 120     setting, so in order to have the masked controls accept characters 
 121     that are specific to your users' language, your application should 
 123     For example, to allow international characters to be used in the 
 124     above masks, you can place the following in your code as part of 
 125     your application's initialization code:: 
 128       locale.setlocale(locale.LC_ALL, '') 
 130     The controls now also support (by popular demand) all "ansi" chars, 
 131     that is, all ascii codes between 32 and 255, by use of the * mask character. 
 133   Using these mask characters, a variety of template masks can be built. See 
 134   the demo for some other common examples include date+time, social security 
 135   number, etc.  If any of these characters are needed as template rather 
 136   than mask characters, they can be escaped with \, ie. \N means "literal N". 
 137   (use \\ for literal backslash, as in: r'CCC\\NNN'.) 
 141       Masks containing only # characters and one optional decimal point 
 142       character are handled specially, as "numeric" controls.  Such 
 143       controls have special handling for typing the '-' key, handling 
 144       the "decimal point" character as truncating the integer portion, 
 145       optionally allowing grouping characters and so forth. 
 146       There are several parameters and format codes that only make sense 
 147       when combined with such masks, eg. groupChar, decimalChar, and so 
 148       forth (see below).  These allow you to construct reasonable 
 149       numeric entry controls. 
 152       Changing the mask for a control deletes any previous field classes 
 153       (and any associated validation or formatting constraints) for them. 
 156   By default, masked edit controls use a fixed width font, so that 
 157   the mask characters are fixed within the control, regardless of 
 158   subsequent modifications to the value.  Set to False if having 
 159   the control font be the same as other controls is required. (This is 
 160   a control-level parameter.) 
 163   (Applies to unicode systems only) By default, the default unicode encoding 
 164   used is latin1, or iso-8859-1.  If necessary, you can set this control-level 
 165   parameter to govern the codec used to decode your keyboard inputs. 
 166   (This is a control-level parameter.) 
 169   These other properties can be passed to the class when instantiating it: 
 170     Formatcodes are specified as a string of single character formatting 
 171     codes that modify  behavior of the control:: 
 176             R  Right-align field(s) 
 177             r  Right-insert in field(s) (implies R) 
 178             <  Stay in field until explicit navigation out of it 
 180             >  Allow insert/delete within partially filled fields (as 
 181                opposed to the default "overwrite" mode for fixed-width 
 182                masked edit controls.)  This allows single-field controls 
 183                or each field within a multi-field control to optionally 
 184                behave more like standard text controls. 
 185                (See EMAIL or phone number autoformat examples.) 
 187                *Note: This also governs whether backspace/delete operations 
 188                shift contents of field to right of cursor, or just blank the 
 191                Also, when combined with 'r', this indicates that the field 
 192                or control allows right insert anywhere within the current 
 193                non-empty value in the field.  (Otherwise right-insert behavior 
 194                is only performed to when the entire right-insertable field is 
 195                selected or the cursor is at the right edge of the field.* 
 198             ,  Allow grouping character in integer fields of numeric controls 
 199                and auto-group/regroup digits (if the result fits) when leaving 
 200                such a field.  (If specified, .SetValue() will attempt to 
 202                ',' is also the default grouping character.  To change the 
 203                grouping character and/or decimal character, use the groupChar 
 204                and decimalChar parameters, respectively. 
 205                Note: typing the "decimal point" character in such fields will 
 206                clip the value to that left of the cursor for integer 
 207                fields of controls with "integer" or "floating point" masks. 
 208                If the ',' format code is specified, this will also cause the 
 209                resulting digits to be regrouped properly, using the current 
 211             -  Prepend and reserve leading space for sign to mask and allow 
 212                signed values (negative #s shown in red by default.) Can be 
 213                used with argument useParensForNegatives (see below.) 
 214             0  integer fields get leading zeros 
 217             F  Auto-Fit: the control calulates its size from 
 218                the length of the template mask 
 219             V  validate entered chars against validRegex before allowing them 
 220                to be entered vs. being allowed by basic mask and then having 
 221                the resulting value just colored as invalid. 
 222                (See USSTATE autoformat demo for how this can be used.) 
 223             S  select entire field when navigating to new field 
 228   These controls have two options for the initial state of the control. 
 229   If a blank control with just the non-editable characters showing 
 230   is desired, simply leave the constructor variable fillChar as its 
 231   default (' ').  If you want some other character there, simply 
 232   change the fillChar to that value.  Note: changing the control's fillChar 
 233   will implicitly reset all of the fields' fillChars to this value. 
 235   If you need different default characters in each mask position, 
 236   you can specify a defaultValue parameter in the constructor, or 
 237   set them for each field individually. 
 238   This value must satisfy the non-editable characters of the mask, 
 239   but need not conform to the replaceable characters. 
 244   These parameters govern what character is used to group numbers 
 245   and is used to indicate the decimal point for numeric format controls. 
 246   The default groupChar is ',', the default decimalChar is '.' 
 247   By changing these, you can customize the presentation of numbers 
 252         formatcodes = ',', groupChar='\''                  allows  12'345.34 
 253         formatcodes = ',', groupChar='.', decimalChar=','  allows  12.345,34 
 255   (These are control-level parameters.) 
 258   The default "shiftDecimalChar" (used for "backwards-tabbing" until 
 259   shift-tab is fixed in wxPython) is '>' (for QUERTY keyboards.) for 
 260   other keyboards, you may want to customize this, eg '?' for shift ',' on 
 261   AZERTY keyboards, ':' or ';' for other European keyboards, etc. 
 262   (This is a control-level parameter.) 
 264 useParensForNegatives=False 
 265   This option can be used with signed numeric format controls to 
 266   indicate signs via () rather than '-'. 
 267   (This is a control-level parameter.) 
 270   This option can be used to have a field or the control try to 
 271   auto-complete on each keystroke if choices have been specified. 
 273 autoCompleteKeycodes=[] 
 274   By default, DownArrow, PageUp and PageDown will auto-complete a 
 275   partially entered field.  Shift-DownArrow, Shift-UpArrow, PageUp 
 276   and PageDown will also auto-complete, but if the field already 
 277   contains a matched value, these keys will cycle through the list 
 278   of choices forward or backward as appropriate.  Shift-Up and 
 279   Shift-Down also take you to the next/previous field after any 
 280   auto-complete action. 
 282   Additional auto-complete keys can be specified via this parameter. 
 283   Any keys so specified will act like PageDown. 
 284   (This is a control-level parameter.) 
 288 Validating User Input 
 289 ===================== 
 290 There are a variety of initialization parameters that are used to validate 
 291 user input.  These parameters can apply to the control as a whole, and/or 
 292 to individual fields: 
 294         =====================  ================================================================== 
 295         excludeChars           A string of characters to exclude even if otherwise allowed 
 296         includeChars           A string of characters to allow even if otherwise disallowed 
 297         validRegex             Use a regular expression to validate the contents of the text box 
 298         validRange             Pass a rangeas list (low,high) to limit numeric fields/values 
 299         choices                A list of strings that are allowed choices for the control. 
 300         choiceRequired         value must be member of choices list 
 301         compareNoCase          Perform case-insensitive matching when validating against list 
 302                                *Note: for masked.ComboBox, this defaults to True.* 
 303         emptyInvalid           Boolean indicating whether an empty value should be considered  
 306         validFunc              A function to call of the form: bool = func(candidate_value) 
 307                                which will return True if the candidate_value satisfies some 
 308                                external criteria for the control in addition to the the 
 309                                other validation, or False if not.  (This validation is 
 310                                applied last in the chain of validations.) 
 312         validRequired          Boolean indicating whether or not keys that are allowed by the 
 313                                mask, but result in an invalid value are allowed to be entered 
 314                                into the control.  Setting this to True implies that a valid 
 315                                default value is set for the control. 
 317         retainFieldValidation  False by default; if True, this allows individual fields to 
 318                                retain their own validation constraints independently of any 
 319                                subsequent changes to the control's overall parameters. 
 320                                (This is a control-level parameter.) 
 322         validator              Validators are not normally needed for masked controls, because 
 323                                of the nature of the validation and control of input.  However, 
 324                                you can supply one to provide data transfer routines for the 
 326         =====================  ================================================================== 
 331   The following parameters have been provided to allow you to change the default 
 332   coloring behavior of the control.   These can be set at construction, or via 
 333   the .SetCtrlParameters() function.  Pass a color as string e.g. 'Yellow': 
 335         ========================  ======================================================================= 
 336         emptyBackgroundColour      Control Background color when identified as empty. Default=White 
 337         invalidBackgroundColour    Control Background color when identified as Not valid. Default=Yellow 
 338         validBackgroundColour      Control Background color when identified as Valid. Default=white 
 339         ========================  ======================================================================= 
 342   The following parameters control the default foreground color coloring behavior of the 
 343   control. Pass a color as string e.g. 'Yellow': 
 345         ========================  ====================================================================== 
 346         foregroundColour           Control foreground color when value is not negative.  Default=Black 
 347         signedForegroundColour     Control foreground color when value is negative. Default=Red 
 348         ========================  ====================================================================== 
 353   Each part of the mask that allows user input is considered a field.  The fields 
 354   are represented by their own class instances.  You can specify field-specific 
 355   constraints by constructing or accessing the field instances for the control 
 356   and then specifying those constraints via parameters. 
 359   This parameter allows you to specify Field instances containing 
 360   constraints for the individual fields of a control, eg: local 
 361   choice lists, validation rules, functions, regexps, etc. 
 362   It can be either an ordered list or a dictionary.  If a list, 
 363   the fields will be applied as fields 0, 1, 2, etc. 
 364   If a dictionary, it should be keyed by field index. 
 365   the values should be a instances of maskededit.Field. 
 367   Any field not represented by the list or dictionary will be 
 368   implicitly created by the control. 
 372     fields = [ Field(formatcodes='_r'), Field('choices=['a', 'b', 'c']) ] 
 377                1: ( Field(formatcodes='_R', choices=['a', 'b', 'c']), 
 378                3: ( Field(choices=['01', '02', '03'], choiceRequired=True) 
 381   The following parameters are available for individual fields, with the 
 382   same semantics as for the whole control but applied to the field in question: 
 384     ==============  ============================================================================= 
 385     fillChar        if set for a field, it will override the control's fillChar for that field 
 386     groupChar       if set for a field, it will override the control's default 
 387     defaultValue    sets field-specific default value; overrides any default from control 
 388     compareNoCase   overrides control's settings 
 389     emptyInvalid    determines whether field is required to be filled at all times 
 390     validRequired   if set, requires field to contain valid value 
 391     ==============  ============================================================================= 
 393   If any of the above parameters are subsequently specified for the control as a 
 394   whole, that new value will be propagated to each field, unless the 
 395   retainFieldValidation control-level parameter is set. 
 397     ==============  ============================== 
 398     formatcodes      Augments control's settings 
 406     ==============  ============================== 
 410 Control Class Functions 
 411 ======================= 
 412 .GetPlainValue(value=None) 
 413                     Returns the value specified (or the control's text value 
 414                     not specified) without the formatting text. 
 415                     In the example above, might return phone no='3522640075', 
 416                     whereas control.GetValue() would return '(352) 264-0075' 
 418                     Returns the control's value to its default, and places the 
 419                     cursor at the beginning of the control. 
 421                     Does "smart replacement" of passed value into the control, as does 
 422                     the .Paste() method.  As with other text entry controls, the 
 423                     .SetValue() text replacement begins at left-edge of the control, 
 424                     with missing mask characters inserted as appropriate. 
 425                     .SetValue will also adjust integer, float or date mask entry values, 
 426                     adding commas, auto-completing years, etc. as appropriate. 
 427                     For "right-aligned" numeric controls, it will also now automatically 
 428                     right-adjust any value whose length is less than the width of the 
 429                     control before attempting to set the value. 
 430                     If a value does not follow the format of the control's mask, or will 
 431                     not fit into the control, a ValueError exception will be raised. 
 435                       mask = '(###) ###-####' 
 436                           .SetValue('1234567890')           => '(123) 456-7890' 
 437                           .SetValue('(123)4567890')         => '(123) 456-7890' 
 438                           .SetValue('(123)456-7890')        => '(123) 456-7890' 
 439                           .SetValue('123/4567-890')         => illegal paste; ValueError 
 441                       mask = '#{6}.#{2}', formatcodes = '_,-', 
 442                           .SetValue('111')                  => ' 111   .  ' 
 443                           .SetValue(' %9.2f' % -111.12345 ) => '   -111.12' 
 444                           .SetValue(' %9.2f' % 1234.00 )    => '  1,234.00' 
 445                           .SetValue(' %9.2f' % -1234567.12345 ) => insufficient room; ValueError 
 447                       mask = '#{6}.#{2}', formatcodes = '_,-R'  # will right-adjust value for right-aligned control 
 448                           .SetValue('111')                  => padded value misalignment ValueError: "       111" will not fit 
 449                           .SetValue('%.2f' % 111 )          => '    111.00' 
 450                           .SetValue('%.2f' % -111.12345 )   => '   -111.12' 
 454                     Returns True if the value specified (or the value of the control 
 455                     if not specified) passes validation tests 
 457                     Returns True if the value specified (or the value of the control 
 458                     if not specified) is equal to an "empty value," ie. all 
 459                     editable characters == the fillChar for their respective fields. 
 460 .IsDefault(value=None) 
 461                     Returns True if the value specified (or the value of the control 
 462                     if not specified) is equal to the initial value of the control. 
 465                     Recolors the control as appropriate to its current settings. 
 467 .SetCtrlParameters(\*\*kwargs) 
 468                     This function allows you to set up and/or change the control parameters 
 469                     after construction; it takes a list of key/value pairs as arguments, 
 470                     where the keys can be any of the mask-specific parameters in the constructor. 
 474                         ctl = masked.TextCtrl( self, -1 ) 
 475                         ctl.SetCtrlParameters( mask='###-####', 
 476                                                defaultValue='555-1212', 
 479 .GetCtrlParameter(parametername) 
 480                     This function allows you to retrieve the current value of a parameter 
 483   *Note:* Each of the control parameters can also be set using its 
 484       own Set and Get function.  These functions follow a regular form: 
 485       All of the parameter names start with lower case; for their 
 486       corresponding Set/Get function, the parameter name is capitalized. 
 490           ctl.SetMask('###-####') 
 491           ctl.SetDefaultValue('555-1212') 
 492           ctl.GetChoiceRequired() 
 495   *Note:* After any change in parameters, the choices for the 
 496       control are reevaluated to ensure that they are still legal.  If you 
 497       have large choice lists, it is therefore more efficient to set parameters 
 498       before setting the choices available. 
 500 .SetFieldParameters(field_index, \*\*kwargs) 
 501                     This function allows you to specify change individual field 
 502                     parameters after construction. (Indices are 0-based.) 
 504 .GetFieldParameter(field_index, parametername) 
 505                     Allows the retrieval of field parameters after construction 
 508 The control detects certain common constructions. In order to use the signed feature 
 509 (negative numbers and coloring), the mask has to be all numbers with optionally one 
 510 decimal point. Without a decimal (e.g. '######', the control will treat it as an integer 
 511 value. With a decimal (e.g. '###.##'), the control will act as a floating point control 
 512 (i.e. press decimal to 'tab' to the decimal position). Pressing decimal in the 
 513 integer control truncates the value.  However, for a true numeric control, 
 514 masked.NumCtrl provides all this, and true numeric input/output support as well. 
 517 Check your controls by calling each control's .IsValid() function and the 
 518 .IsEmpty() function to determine which controls have been a) filled in and 
 519 b) filled in properly. 
 522 Regular expression validations can be used flexibly and creatively. 
 523 Take a look at the demo; the zip-code validation succeeds as long as the 
 524 first five numerals are entered. the last four are optional, but if 
 525 any are entered, there must be 4 to be valid. 
 527 masked.Ctrl Configuration 
 528 ========================= 
 529 masked.Ctrl works by looking for a special *controlType* 
 530 parameter in the variable arguments of the control, to determine 
 531 what kind of instance to return. 
 532 controlType can be one of:: 
 540 These constants are also available individually, ie, you can 
 541 use either of the following:: 
 543     from wxPython.wx.lib.masked import MaskedCtrl, controlTypes 
 544     from wxPython.wx.lib.masked import MaskedCtrl, COMBO, TEXT, NUMBER, IPADDR 
 546 If not specified as a keyword argument, the default controlType is 
 552 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 
 557   All methods of the Mixin that are not meant to be exposed to the external 
 558   interface are prefaced with '_'.  Those functions that are primarily 
 559   intended to be internal subroutines subsequently start with a lower-case 
 560   letter; those that are primarily intended to be used and/or overridden 
 561   by derived subclasses start with a capital letter. 
 563   The following methods must be used and/or defined when deriving a control 
 564   from MaskedEditMixin.  NOTE: if deriving from a *masked edit* control 
 565   (eg. class IpAddrCtrl(masked.TextCtrl) ), then this is NOT necessary, 
 566   as it's already been done for you in the base class. 
 569                         This function must be called after the associated base 
 570                         control has been initialized in the subclass __init__ 
 571                         function.  It sets the initial value of the control, 
 572                         either to the value specified if non-empty, the 
 573                         default value if specified, or the "template" for 
 574                         the empty control as necessary.  It will also set/reset 
 575                         the font if necessary and apply formatting to the 
 576                         control at this time. 
 580                         Each class derived from MaskedEditMixin must define 
 581                         the function for getting the start and end of the 
 582                         current text selection.  The reason for this is 
 583                         that not all controls have the same function name for 
 584                         doing this; eg. wx.TextCtrl uses .GetSelection(), 
 585                         whereas we had to write a .GetMark() function for 
 586                         wxComboBox, because .GetSelection() for the control 
 587                         gets the currently selected list item from the combo 
 588                         box, and the control doesn't (yet) natively provide 
 589                         a means of determining the text selection. 
 592                         Similarly to _GetSelection, each class derived from 
 593                         MaskedEditMixin must define the function for setting 
 594                         the start and end of the current text selection. 
 595                         (eg. .SetSelection() for masked.TextCtrl, and .SetMark() for 
 598         ._GetInsertionPoint() 
 599         ._SetInsertionPoint() 
 601                         For consistency, and because the mixin shouldn't rely 
 602                         on fixed names for any manipulations it does of any of 
 603                         the base controls, we require each class derived from 
 604                         MaskedEditMixin to define these functions as well. 
 607         ._SetValue()    REQUIRED 
 608                         Each class derived from MaskedEditMixin must define 
 609                         the functions used to get and set the raw value of the 
 611                         This is necessary so that recursion doesn't take place 
 612                         when setting the value, and so that the mixin can 
 613                         call the appropriate function after doing all its 
 614                         validation and manipulation without knowing what kind 
 615                         of base control it was mixed in with.  To handle undo 
 616                         functionality, the ._SetValue() must record the current 
 617                         selection prior to setting the value. 
 623                         Each class derived from MaskedEditMixin must redefine 
 624                         these functions to call the _Cut(), _Paste(), _Undo() 
 625                         and _SetValue() methods, respectively for the control, 
 626                         so as to prevent programmatic corruption of the control's 
 627                         value.  This must be done in each derivation, as the 
 628                         mixin cannot itself override a member of a sibling class. 
 631                         Each class derived from MaskedEditMixin must define 
 632                         the function used to refresh the base control. 
 635                         Each class derived from MaskedEditMixin must redefine 
 636                         this function so that it checks the validity of the 
 637                         control (via self._CheckValid) and then refreshes 
 638                         control using the base class method. 
 640         ._IsEditable()  REQUIRED 
 641                         Each class derived from MaskedEditMixin must define 
 642                         the function used to determine if the base control is 
 643                         editable or not.  (For masked.ComboBox, this has to 
 644                         be done with code, rather than specifying the proper 
 645                         function in the base control, as there isn't one...) 
 646         ._CalcSize()    REQUIRED 
 647                         Each class derived from MaskedEditMixin must define 
 648                         the function used to determine how wide the control 
 649                         should be given the mask.  (The mixin function 
 650                         ._calcSize() provides a baseline estimate.) 
 655   Event handlers are "chained", and MaskedEditMixin usually 
 656   swallows most of the events it sees, thereby preventing any other 
 657   handlers from firing in the chain.  It is therefore required that 
 658   each class derivation using the mixin to have an option to hook up 
 659   the event handlers itself or forego this operation and let a 
 660   subclass of the masked control do so.  For this reason, each 
 661   subclass should probably include the following code: 
 663     if setupEventHandling: 
 664         ## Setup event handlers 
 665         EVT_SET_FOCUS( self, self._OnFocus )        ## defeat automatic full selection 
 666         EVT_KILL_FOCUS( self, self._OnKillFocus )   ## run internal validator 
 667         EVT_LEFT_DCLICK(self, self._OnDoubleClick)  ## select field under cursor on dclick 
 668         EVT_RIGHT_UP(self, self._OnContextMenu )    ## bring up an appropriate context menu 
 669         EVT_KEY_DOWN( self, self._OnKeyDown )       ## capture control events not normally seen, eg ctrl-tab. 
 670         EVT_CHAR( self, self._OnChar )              ## handle each keypress 
 671         EVT_TEXT( self, self.GetId(), self._OnTextChange )  ## color control appropriately & keep 
 672                                                             ## track of previous value for undo 
 674   where setupEventHandling is an argument to its constructor. 
 676   These 5 handlers must be "wired up" for the masked edit 
 677   controls to provide default behavior.  (The setupEventHandling 
 678   is an argument to masked.TextCtrl and masked.ComboBox, so 
 679   that controls derived from *them* may replace one of these 
 680   handlers if they so choose.) 
 682   If your derived control wants to preprocess events before 
 683   taking action, it should then set up the event handling itself, 
 684   so it can be first in the event handler chain. 
 687   The following routines are available to facilitate changing 
 688   the default behavior of masked edit controls: 
 690         ._SetKeycodeHandler(keycode, func) 
 691         ._SetKeyHandler(char, func) 
 692                         Use to replace default handling for any given keycode. 
 693                         func should take the key event as argument and return 
 694                         False if no further action is required to handle the 
 696                             self._SetKeycodeHandler(WXK_UP, self.IncrementValue) 
 697                             self._SetKeyHandler('-', self._OnChangeSign) 
 699         "Navigation" keys are assumed to change the cursor position, and 
 700         therefore don't cause automatic motion of the cursor as insertable 
 703         ._AddNavKeycode(keycode, handler=None) 
 704         ._AddNavKey(char, handler=None) 
 705                         Allows controls to specify other keys (and optional handlers) 
 706                         to be treated as navigational characters. (eg. '.' in IpAddrCtrl) 
 708         ._GetNavKeycodes()  Returns the current list of navigational keycodes. 
 710         ._SetNavKeycodes(key_func_tuples) 
 711                         Allows replacement of the current list of keycode 
 712                         processed as navigation keys, and bind associated 
 713                         optional keyhandlers. argument is a list of key/handler 
 714                         tuples.  Passing a value of None for the handler in a 
 715                         given tuple indicates that default processing for the key 
 718         ._FindField(pos) Returns the Field object associated with this position 
 721         ._FindFieldExtent(pos, getslice=False, value=None) 
 722                         Returns edit_start, edit_end of the field corresponding 
 723                         to the specified position within the control, and 
 724                         optionally also returns the current contents of that field. 
 725                         If value is specified, it will retrieve the slice the corresponding 
 726                         slice from that value, rather than the current value of the 
 730                         This is, the function that gets called for a given position 
 731                         whenever the cursor is adjusted to leave a given field. 
 732                         By default, it adjusts the year in date fields if mask is a date, 
 733                         It can be overridden by a derived class to 
 734                         adjust the value of the control at that time. 
 735                         (eg. IpAddrCtrl reformats the address in this way.) 
 737         ._Change()      Called by internal EVT_TEXT handler. Return False to force 
 738                         skip of the normal class change event. 
 739         ._Keypress(key) Called by internal EVT_CHAR handler. Return False to force 
 740                         skip of the normal class keypress event. 
 741         ._LostFocus()   Called by internal EVT_KILL_FOCUS handler 
 744                         This is the default EVT_KEY_DOWN routine; it just checks for 
 745                         "navigation keys", and if event.ControlDown(), it fires the 
 746                         mixin's _OnChar() routine, as such events are not always seen 
 747                         by the "cooked" EVT_CHAR routine. 
 749         ._OnChar(event) This is the main EVT_CHAR handler for the 
 752     The following routines are used to handle standard actions 
 754         _OnArrow(event)         used for arrow navigation events 
 755         _OnCtrl_A(event)        'select all' 
 756         _OnCtrl_C(event)        'copy' (uses base control function, as copy is non-destructive) 
 757         _OnCtrl_S(event)        'save' (does nothing) 
 758         _OnCtrl_V(event)        'paste' - calls _Paste() method, to do smart paste 
 759         _OnCtrl_X(event)        'cut'   - calls _Cut() method, to "erase" selection 
 760         _OnCtrl_Z(event)        'undo'  - resets value to previous value (if any) 
 762         _OnChangeField(event)   primarily used for tab events, but can be 
 763                                 used for other keys (eg. '.' in IpAddrCtrl) 
 765         _OnErase(event)         used for backspace and delete 
 769     The following routine provides a hook back to any class derivations, so that 
 770     they can react to parameter changes before any value is set/reset as a result of 
 771     those changes.  (eg. masked.ComboBox needs to detect when the choices list is 
 772     modified, either implicitly or explicitly, so it can reset the base control 
 773     to have the appropriate choice list *before* the initial value is reset to match.) 
 775         _OnCtrlParametersChanged() 
 779     For convenience, each class derived from MaskedEditMixin should 
 780     define an accessors mixin, so that it exposes only those parameters 
 781     that make sense for the derivation.  This is done with an intermediate 
 782     level of inheritance, ie: 
 784     class BaseMaskedTextCtrl( TextCtrl, MaskedEditMixin ): 
 786     class TextCtrl( BaseMaskedTextCtrl, MaskedEditAccessorsMixin ): 
 787     class ComboBox( BaseMaskedComboBox, MaskedEditAccessorsMixin ): 
 788     class NumCtrl( BaseMaskedTextCtrl, MaskedNumCtrlAccessorsMixin ): 
 789     class IpAddrCtrl( BaseMaskedTextCtrl, IpAddrCtrlAccessorsMixin ): 
 790     class TimeCtrl( BaseMaskedTextCtrl, TimeCtrlAccessorsMixin ): 
 794     Each accessors mixin defines Get/Set functions for the base class parameters 
 795     that are appropriate for that derivation. 
 796     This allows the base classes to be "more generic," exposing the widest 
 797     set of options, while not requiring derived classes to be so general. 
 808 # jmg 12/9/03 - when we cut ties with Py 2.2 and earlier, this would 
 809 # be a good place to implement the 2.3 logger class 
 810 from wx
.tools
.dbg 
import Logger
 
 815 ## ---------- ---------- ---------- ---------- ---------- ---------- ---------- 
 817 ## Constants for identifying control keys and classes of keys: 
 819 WXK_CTRL_A 
= (ord('A')+1) - ord('A')   ## These keys are not already defined in wx 
 820 WXK_CTRL_C 
= (ord('C')+1) - ord('A') 
 821 WXK_CTRL_S 
= (ord('S')+1) - ord('A') 
 822 WXK_CTRL_V 
= (ord('V')+1) - ord('A') 
 823 WXK_CTRL_X 
= (ord('X')+1) - ord('A') 
 824 WXK_CTRL_Z 
= (ord('Z')+1) - ord('A') 
 827     wx
.WXK_BACK
, wx
.WXK_LEFT
, wx
.WXK_RIGHT
, wx
.WXK_UP
, wx
.WXK_DOWN
, wx
.WXK_TAB
, 
 828     wx
.WXK_HOME
, wx
.WXK_END
, wx
.WXK_RETURN
, wx
.WXK_PRIOR
, wx
.WXK_NEXT
 
 832     wx
.WXK_BACK
, wx
.WXK_DELETE
, WXK_CTRL_A
, WXK_CTRL_C
, WXK_CTRL_S
, WXK_CTRL_V
, 
 833     WXK_CTRL_X
, WXK_CTRL_Z
 
 837 ## ---------- ---------- ---------- ---------- ---------- ---------- ---------- 
 839 ## Constants for masking. This is where mask characters 
 841 ##  maskchars used to identify valid mask characters from all others 
 842 ##   #- allow numeric 0-9 only 
 843 ##   A- allow uppercase only. Combine with forceupper to force lowercase to upper 
 844 ##   a- allow lowercase only. Combine with forcelower to force upper to lowercase 
 845 ##   X- allow any character (string.letters, string.punctuation, string.digits) 
 846 ## Note: locale settings affect what "uppercase", lowercase, etc comprise. 
 848 maskchars 
= ("#","A","a","X","C","N",'*','&') 
 850 for i 
in xrange(32, 256): 
 853 months 
= '(01|02|03|04|05|06|07|08|09|10|11|12)' 
 854 charmonths 
= '(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec|JAN|FEB|MAR|APR|MAY|JUN|JUL|AUG|SEP|OCT|NOV|DEC|jan|feb|mar|apr|may|jun|jul|aug|sep|oct|nov|dec)' 
 855 charmonths_dict 
= {'jan': 1, 'feb': 2, 'mar': 3, 'apr': 4, 'may': 5, 'jun': 6, 
 856                    'jul': 7, 'aug': 8, 'sep': 9, 'oct': 10, 'nov': 11, 'dec': 12} 
 858 days   
= '(01|02|03|04|05|06|07|08|09|10|11|12|13|14|15|16|17|18|19|20|21|22|23|24|25|26|27|28|29|30|31)' 
 859 hours  
= '(0\d| \d|1[012])' 
 860 milhours 
= '(00|01|02|03|04|05|06|07|08|09|10|11|12|13|14|15|16|17|18|19|20|21|22|23)' 
 861 minutes 
= """(00|01|02|03|04|05|06|07|08|09|10|11|12|13|14|15|\ 
 862 16|17|18|19|20|21|22|23|24|25|26|27|28|29|30|31|32|33|34|35|\ 
 863 36|37|38|39|40|41|42|43|44|45|46|47|48|49|50|51|52|53|54|55|\ 
 866 am_pm_exclude 
= 'BCDEFGHIJKLMNOQRSTUVWXYZ\x8a\x8c\x8e\x9f\xc0\xc1\xc2\xc3\xc4\xc5\xc6\xc7\xc8\xc9\xca\xcb\xcc\xcd\xce\xcf\xd0\xd1\xd2\xd3\xd4\xd5\xd6\xd8\xd9\xda\xdb\xdc\xdd\xde' 
 868 states 
= "AL,AK,AZ,AR,CA,CO,CT,DE,DC,FL,GA,GU,HI,ID,IL,IN,IA,KS,KY,LA,MA,ME,MD,MI,MN,MS,MO,MT,NE,NV,NH,NJ,NM,NY,NC,ND,OH,OK,OR,PA,PR,RI,SC,SD,TN,TX,UT,VA,VT,VI,WA,WV,WI,WY".split(',') 
 870 state_names 
= ['Alabama','Alaska','Arizona','Arkansas', 
 871                'California','Colorado','Connecticut', 
 872                'Delaware','District of Columbia', 
 873                'Florida','Georgia','Hawaii', 
 874                'Idaho','Illinois','Indiana','Iowa', 
 875                'Kansas','Kentucky','Louisiana', 
 876                'Maine','Maryland','Massachusetts','Michigan', 
 877                'Minnesota','Mississippi','Missouri','Montana', 
 878                'Nebraska','Nevada','New Hampshire','New Jersey', 
 879                'New Mexico','New York','North Carolina','North Dakokta', 
 880                'Ohio','Oklahoma','Oregon', 
 881                'Pennsylvania','Puerto Rico','Rhode Island', 
 882                'South Carolina','South Dakota', 
 883                'Tennessee','Texas','Utah', 
 884                'Vermont','Virginia', 
 885                'Washington','West Virginia', 
 886                'Wisconsin','Wyoming'] 
 888 ## ---------- ---------- ---------- ---------- ---------- ---------- ---------- 
 890 ## The following dictionary defines the current set of autoformats: 
 894            'mask': "(###) ###-#### x:###", 
 895            'formatcodes': 'F^->', 
 896            'validRegex': "^\(\d{3}\) \d{3}-\d{4}", 
 897            'description': "Phone Number w/opt. ext" 
 900            'mask': "###-###-#### x:###", 
 901            'formatcodes': 'F^->', 
 902            'validRegex': "^\d{3}-\d{3}-\d{4}", 
 903            'description': "Phone Number\n (w/hyphens and opt. ext)" 
 906            'mask': "(###) ###-####", 
 907            'formatcodes': 'F^->', 
 908            'validRegex': "^\(\d{3}\) \d{3}-\d{4}", 
 909            'description': "Phone Number only" 
 912            'mask': "###-###-####", 
 913            'formatcodes': 'F^->', 
 914            'validRegex': "^\d{3}-\d{3}-\d{4}", 
 915            'description': "Phone Number\n(w/hyphens)" 
 919            'formatcodes': 'F!V', 
 920            'validRegex': "([ACDFGHIKLMNOPRSTUVW] |%s)" % string
.join(states
,'|'), 
 922            'choiceRequired': True, 
 923            'description': "US State Code" 
 926            'mask': "ACCCCCCCCCCCCCCCCCCC", 
 928            'validRegex': "([ACDFGHIKLMNOPRSTUVW] |%s)" % string
.join(state_names
,'|'), 
 929            'choices': state_names
, 
 930            'choiceRequired': True, 
 931            'description': "US State Name" 
 934        "USDATETIMEMMDDYYYY/HHMMSS": { 
 935            'mask': "##/##/#### ##:##:## AM", 
 936            'excludeChars': am_pm_exclude
, 
 937            'formatcodes': 'DF!', 
 938            'validRegex': '^' + months 
+ '/' + days 
+ '/' + '\d{4} ' + hours 
+ ':' + minutes 
+ ':' + seconds 
+ ' (A|P)M', 
 939            'description': "US Date + Time" 
 941        "USDATETIMEMMDDYYYY-HHMMSS": { 
 942            'mask': "##-##-#### ##:##:## AM", 
 943            'excludeChars': am_pm_exclude
, 
 944            'formatcodes': 'DF!', 
 945            'validRegex': '^' + months 
+ '-' + days 
+ '-' + '\d{4} ' + hours 
+ ':' + minutes 
+ ':' + seconds 
+ ' (A|P)M', 
 946            'description': "US Date + Time\n(w/hypens)" 
 948        "USDATE24HRTIMEMMDDYYYY/HHMMSS": { 
 949            'mask': "##/##/#### ##:##:##", 
 951            'validRegex': '^' + months 
+ '/' + days 
+ '/' + '\d{4} ' + milhours 
+ ':' + minutes 
+ ':' + seconds
, 
 952            'description': "US Date + 24Hr (Military) Time" 
 954        "USDATE24HRTIMEMMDDYYYY-HHMMSS": { 
 955            'mask': "##-##-#### ##:##:##", 
 957            'validRegex': '^' + months 
+ '-' + days 
+ '-' + '\d{4} ' + milhours 
+ ':' + minutes 
+ ':' + seconds
, 
 958            'description': "US Date + 24Hr Time\n(w/hypens)" 
 960        "USDATETIMEMMDDYYYY/HHMM": { 
 961            'mask': "##/##/#### ##:## AM", 
 962            'excludeChars': am_pm_exclude
, 
 963            'formatcodes': 'DF!', 
 964            'validRegex': '^' + months 
+ '/' + days 
+ '/' + '\d{4} ' + hours 
+ ':' + minutes 
+ ' (A|P)M', 
 965            'description': "US Date + Time\n(without seconds)" 
 967        "USDATE24HRTIMEMMDDYYYY/HHMM": { 
 968            'mask': "##/##/#### ##:##", 
 970            'validRegex': '^' + months 
+ '/' + days 
+ '/' + '\d{4} ' + milhours 
+ ':' + minutes
, 
 971            'description': "US Date + 24Hr Time\n(without seconds)" 
 973        "USDATETIMEMMDDYYYY-HHMM": { 
 974            'mask': "##-##-#### ##:## AM", 
 975            'excludeChars': am_pm_exclude
, 
 976            'formatcodes': 'DF!', 
 977            'validRegex': '^' + months 
+ '-' + days 
+ '-' + '\d{4} ' + hours 
+ ':' + minutes 
+ ' (A|P)M', 
 978            'description': "US Date + Time\n(w/hypens and w/o secs)" 
 980        "USDATE24HRTIMEMMDDYYYY-HHMM": { 
 981            'mask': "##-##-#### ##:##", 
 983            'validRegex': '^' + months 
+ '-' + days 
+ '-' + '\d{4} ' + milhours 
+ ':' + minutes
, 
 984            'description': "US Date + 24Hr Time\n(w/hyphens and w/o seconds)" 
 987            'mask': "##/##/####", 
 989            'validRegex': '^' + months 
+ '/' + days 
+ '/' + '\d{4}', 
 990            'description': "US Date\n(MMDDYYYY)" 
 995            'validRegex': '^' + months 
+ '/' + days 
+ '/\d\d', 
 996            'description': "US Date\n(MMDDYY)" 
 999            'mask': "##-##-####", 
1000            'formatcodes': 'DF', 
1001            'validRegex': '^' + months 
+ '-' + days 
+ '-' +'\d{4}', 
1002            'description': "MM-DD-YYYY" 
1005        "EUDATEYYYYMMDD/": { 
1006            'mask': "####/##/##", 
1007            'formatcodes': 'DF', 
1008            'validRegex': '^' + '\d{4}'+ '/' + months 
+ '/' + days
, 
1009            'description': "YYYY/MM/DD" 
1011        "EUDATEYYYYMMDD.": { 
1012            'mask': "####.##.##", 
1013            'formatcodes': 'DF', 
1014            'validRegex': '^' + '\d{4}'+ '.' + months 
+ '.' + days
, 
1015            'description': "YYYY.MM.DD" 
1017        "EUDATEDDMMYYYY/": { 
1018            'mask': "##/##/####", 
1019            'formatcodes': 'DF', 
1020            'validRegex': '^' + days 
+ '/' + months 
+ '/' + '\d{4}', 
1021            'description': "DD/MM/YYYY" 
1023        "EUDATEDDMMYYYY.": { 
1024            'mask': "##.##.####", 
1025            'formatcodes': 'DF', 
1026            'validRegex': '^' + days 
+ '.' + months 
+ '.' + '\d{4}', 
1027            'description': "DD.MM.YYYY" 
1029        "EUDATEDDMMMYYYY.": { 
1030            'mask': "##.CCC.####", 
1031            'formatcodes': 'DF', 
1032            'validRegex': '^' + days 
+ '.' + charmonths 
+ '.' + '\d{4}', 
1033            'description': "DD.Month.YYYY" 
1035        "EUDATEDDMMMYYYY/": { 
1036            'mask': "##/CCC/####", 
1037            'formatcodes': 'DF', 
1038            'validRegex': '^' + days 
+ '/' + charmonths 
+ '/' + '\d{4}', 
1039            'description': "DD/Month/YYYY" 
1042        "EUDATETIMEYYYYMMDD/HHMMSS": { 
1043            'mask': "####/##/## ##:##:## AM", 
1044            'excludeChars': am_pm_exclude
, 
1045            'formatcodes': 'DF!', 
1046            'validRegex': '^' + '\d{4}'+ '/' + months 
+ '/' + days 
+ ' ' + hours 
+ ':' + minutes 
+ ':' + seconds 
+ ' (A|P)M', 
1047            'description': "YYYY/MM/DD HH:MM:SS" 
1049        "EUDATETIMEYYYYMMDD.HHMMSS": { 
1050            'mask': "####.##.## ##:##:## AM", 
1051            'excludeChars': am_pm_exclude
, 
1052            'formatcodes': 'DF!', 
1053            'validRegex': '^' + '\d{4}'+ '.' + months 
+ '.' + days 
+ ' ' + hours 
+ ':' + minutes 
+ ':' + seconds 
+ ' (A|P)M', 
1054            'description': "YYYY.MM.DD HH:MM:SS" 
1056        "EUDATETIMEDDMMYYYY/HHMMSS": { 
1057            'mask': "##/##/#### ##:##:## AM", 
1058            'excludeChars': am_pm_exclude
, 
1059            'formatcodes': 'DF!', 
1060            'validRegex': '^' + days 
+ '/' + months 
+ '/' + '\d{4} ' + hours 
+ ':' + minutes 
+ ':' + seconds 
+ ' (A|P)M', 
1061            'description': "DD/MM/YYYY HH:MM:SS" 
1063        "EUDATETIMEDDMMYYYY.HHMMSS": { 
1064            'mask': "##.##.#### ##:##:## AM", 
1065            'excludeChars': am_pm_exclude
, 
1066            'formatcodes': 'DF!', 
1067            'validRegex': '^' + days 
+ '.' + months 
+ '.' + '\d{4} ' + hours 
+ ':' + minutes 
+ ':' + seconds 
+ ' (A|P)M', 
1068            'description': "DD.MM.YYYY HH:MM:SS" 
1071        "EUDATETIMEYYYYMMDD/HHMM": { 
1072            'mask': "####/##/## ##:## AM", 
1073            'excludeChars': am_pm_exclude
, 
1074            'formatcodes': 'DF!', 
1075            'validRegex': '^' + '\d{4}'+ '/' + months 
+ '/' + days 
+ ' ' + hours 
+ ':' + minutes 
+ ' (A|P)M', 
1076            'description': "YYYY/MM/DD HH:MM" 
1078        "EUDATETIMEYYYYMMDD.HHMM": { 
1079            'mask': "####.##.## ##:## AM", 
1080            'excludeChars': am_pm_exclude
, 
1081            'formatcodes': 'DF!', 
1082            'validRegex': '^' + '\d{4}'+ '.' + months 
+ '.' + days 
+ ' ' + hours 
+ ':' + minutes 
+ ' (A|P)M', 
1083            'description': "YYYY.MM.DD HH:MM" 
1085        "EUDATETIMEDDMMYYYY/HHMM": { 
1086            'mask': "##/##/#### ##:## AM", 
1087            'excludeChars': am_pm_exclude
, 
1088            'formatcodes': 'DF!', 
1089            'validRegex': '^' + days 
+ '/' + months 
+ '/' + '\d{4} ' + hours 
+ ':' + minutes 
+ ' (A|P)M', 
1090            'description': "DD/MM/YYYY HH:MM" 
1092        "EUDATETIMEDDMMYYYY.HHMM": { 
1093            'mask': "##.##.#### ##:## AM", 
1094            'excludeChars': am_pm_exclude
, 
1095            'formatcodes': 'DF!', 
1096            'validRegex': '^' + days 
+ '.' + months 
+ '.' + '\d{4} ' + hours 
+ ':' + minutes 
+ ' (A|P)M', 
1097            'description': "DD.MM.YYYY HH:MM" 
1100        "EUDATE24HRTIMEYYYYMMDD/HHMMSS": { 
1101            'mask': "####/##/## ##:##:##", 
1102            'formatcodes': 'DF', 
1103            'validRegex': '^' + '\d{4}'+ '/' + months 
+ '/' + days 
+ ' ' + milhours 
+ ':' + minutes 
+ ':' + seconds
, 
1104            'description': "YYYY/MM/DD 24Hr Time" 
1106        "EUDATE24HRTIMEYYYYMMDD.HHMMSS": { 
1107            'mask': "####.##.## ##:##:##", 
1108            'formatcodes': 'DF', 
1109            'validRegex': '^' + '\d{4}'+ '.' + months 
+ '.' + days 
+ ' ' + milhours 
+ ':' + minutes 
+ ':' + seconds
, 
1110            'description': "YYYY.MM.DD 24Hr Time" 
1112        "EUDATE24HRTIMEDDMMYYYY/HHMMSS": { 
1113            'mask': "##/##/#### ##:##:##", 
1114            'formatcodes': 'DF', 
1115            'validRegex': '^' + days 
+ '/' + months 
+ '/' + '\d{4} ' + milhours 
+ ':' + minutes 
+ ':' + seconds
, 
1116            'description': "DD/MM/YYYY 24Hr Time" 
1118        "EUDATE24HRTIMEDDMMYYYY.HHMMSS": { 
1119            'mask': "##.##.#### ##:##:##", 
1120            'formatcodes': 'DF', 
1121            'validRegex': '^' + days 
+ '.' + months 
+ '.' + '\d{4} ' + milhours 
+ ':' + minutes 
+ ':' + seconds
, 
1122            'description': "DD.MM.YYYY 24Hr Time" 
1124        "EUDATE24HRTIMEYYYYMMDD/HHMM": { 
1125            'mask': "####/##/## ##:##", 
1126            'formatcodes': 'DF','validRegex': '^' + '\d{4}'+ '/' + months 
+ '/' + days 
+ ' ' + milhours 
+ ':' + minutes
, 
1127            'description': "YYYY/MM/DD 24Hr Time\n(w/o seconds)" 
1129        "EUDATE24HRTIMEYYYYMMDD.HHMM": { 
1130            'mask': "####.##.## ##:##", 
1131            'formatcodes': 'DF', 
1132            'validRegex': '^' + '\d{4}'+ '.' + months 
+ '.' + days 
+ ' ' + milhours 
+ ':' + minutes
, 
1133            'description': "YYYY.MM.DD 24Hr Time\n(w/o seconds)" 
1135        "EUDATE24HRTIMEDDMMYYYY/HHMM": { 
1136            'mask': "##/##/#### ##:##", 
1137            'formatcodes': 'DF', 
1138            'validRegex': '^' + days 
+ '/' + months 
+ '/' + '\d{4} ' + milhours 
+ ':' + minutes
, 
1139            'description': "DD/MM/YYYY 24Hr Time\n(w/o seconds)" 
1141        "EUDATE24HRTIMEDDMMYYYY.HHMM": { 
1142            'mask': "##.##.#### ##:##", 
1143            'formatcodes': 'DF', 
1144            'validRegex': '^' + days 
+ '.' + months 
+ '.' + '\d{4} ' + milhours 
+ ':' + minutes
, 
1145            'description': "DD.MM.YYYY 24Hr Time\n(w/o seconds)" 
1149            'mask': "##:##:## AM", 
1150            'excludeChars': am_pm_exclude
, 
1151            'formatcodes': 'TF!', 
1152            'validRegex': '^' + hours 
+ ':' + minutes 
+ ':' + seconds 
+ ' (A|P)M', 
1153            'description': "HH:MM:SS (A|P)M\n(see TimeCtrl)" 
1157            'excludeChars': am_pm_exclude
, 
1158            'formatcodes': 'TF!', 
1159            'validRegex': '^' + hours 
+ ':' + minutes 
+ ' (A|P)M', 
1160            'description': "HH:MM (A|P)M\n(see TimeCtrl)" 
1164            'formatcodes': 'TF', 
1165            'validRegex': '^' + milhours 
+ ':' + minutes 
+ ':' + seconds
, 
1166            'description': "24Hr HH:MM:SS\n(see TimeCtrl)" 
1170            'formatcodes': 'TF', 
1171            'validRegex': '^' + milhours 
+ ':' + minutes
, 
1172            'description': "24Hr HH:MM\n(see TimeCtrl)" 
1175            'mask': "###-##-####", 
1177            'validRegex': "\d{3}-\d{2}-\d{4}", 
1178            'description': "Social Sec#" 
1181            'mask': "####-####-####-####", 
1183            'validRegex': "\d{4}-\d{4}-\d{4}-\d{4}", 
1184            'description': "Credit Card" 
1189            'validRegex': "^" + months 
+ "/\d\d", 
1190            'description': "Expiration MM/YY" 
1195            'validRegex': "^\d{5}", 
1196            'description': "US 5-digit zip code" 
1199            'mask': "#####-####", 
1201            'validRegex': "\d{5}-(\s{4}|\d{4})", 
1202            'description': "US zip+4 code" 
1207            'validRegex': "^0.\d\d", 
1208            'description': "Percentage" 
1213            'validRegex': "^[1-9]{1}  |[1-9][0-9] |1[0|1|2][0-9]", 
1214            'description': "Age" 
1217            'mask': "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX", 
1218            'excludeChars': " \\/*&%$#!+='\"", 
1219            'formatcodes': "F>", 
1220            'validRegex': "^\w+([\-\.]\w+)*@((([a-zA-Z0-9]+(\-[a-zA-Z0-9]+)*\.)+)[a-zA-Z]{2,4}|\[(\d|\d\d|(1\d\d|2[0-4]\d|25[0-5]))(\.(\d|\d\d|(1\d\d|2[0-4]\d|25[0-5]))){3}\]) *$", 
1221            'description': "Email address" 
1224            'mask': "###.###.###.###", 
1225            'formatcodes': 'F_Sr', 
1226            'validRegex': "(  \d| \d\d|(1\d\d|2[0-4]\d|25[0-5]))(\.(  \d| \d\d|(1\d\d|2[0-4]\d|25[0-5]))){3}", 
1227            'description': "IP Address\n(see IpAddrCtrl)" 
1231 # build demo-friendly dictionary of descriptions of autoformats 
1233 for key
, value 
in masktags
.items(): 
1234     autoformats
.append((key
, value
['description'])) 
1237 ## ---------- ---------- ---------- ---------- ---------- ---------- ---------- 
1241     This class manages the individual fields in a masked edit control. 
1242     Each field has a zero-based index, indicating its position in the 
1243     control, an extent, an associated mask, and a plethora of optional 
1244     parameters.  Fields can be instantiated and then associated with 
1245     parent masked controls, in order to provide field-specific configuration. 
1246     Alternatively, fields will be implicitly created by the parent control 
1247     if not provided at construction, at which point, the fields can then 
1248     manipulated by the controls .SetFieldParameters() method. 
1251               'index': None,                    ## which field of mask; set by parent control. 
1252               'mask': "",                       ## mask chars for this field 
1253               'extent': (),                     ## (edit start, edit_end) of field; set by parent control. 
1254               'formatcodes':  "",               ## codes indicating formatting options for the control 
1255               'fillChar':     ' ',              ## used as initial value for each mask position if initial value is not given 
1256               'groupChar':    ',',              ## used with numeric fields; indicates what char groups 3-tuple digits 
1257               'decimalChar':  '.',              ## used with numeric fields; indicates what char separates integer from fraction 
1258               'shiftDecimalChar': '>',          ## used with numeric fields, indicates what is above the decimal point char on keyboard 
1259               'useParensForNegatives': False,   ## used with numeric fields, indicates that () should be used vs. - to show negative numbers. 
1260               'defaultValue': "",               ## use if you want different positional defaults vs. all the same fillChar 
1261               'excludeChars': "",               ## optional string of chars to exclude even if main mask type does 
1262               'includeChars': "",               ## optional string of chars to allow even if main mask type doesn't 
1263               'validRegex':   "",               ## optional regular expression to use to validate the control 
1264               'validRange':   (),               ## Optional hi-low range for numerics 
1265               'choices':    [],                 ## Optional list for character expressions 
1266               'choiceRequired': False,          ## If choices supplied this specifies if valid value must be in the list 
1267               'compareNoCase': False,           ## Optional flag to indicate whether or not to use case-insensitive list search 
1268               'autoSelect': False,              ## Set to True to try auto-completion on each keystroke: 
1269               'validFunc': None,                ## Optional function for defining additional, possibly dynamic validation constraints on contrl 
1270               'validRequired': False,           ## Set to True to disallow input that results in an invalid value 
1271               'emptyInvalid':  False,           ## Set to True to make EMPTY = INVALID 
1272               'description': "",                ## primarily for autoformats, but could be useful elsewhere 
1275     # This list contains all parameters that when set at the control level should 
1276     # propagate down to each field: 
1277     propagating_params 
= ('fillChar', 'groupChar', 'decimalChar','useParensForNegatives', 
1278                           'compareNoCase', 'emptyInvalid', 'validRequired') 
1280     def __init__(self
, **kwargs
): 
1282         This is the "constructor" for setting up parameters for fields. 
1283         a field_index of -1 is used to indicate "the entire control." 
1285 ####        dbg('Field::Field', indent=1) 
1286         # Validate legitimate set of parameters: 
1287         for key 
in kwargs
.keys(): 
1288             if key 
not in Field
.valid_params
.keys(): 
1290                 raise TypeError('invalid parameter "%s"' % (key
)) 
1292         # Set defaults for each parameter for this instance, and fully 
1293         # populate initial parameter list for configuration: 
1294         for key
, value 
in Field
.valid_params
.items(): 
1295             setattr(self
, '_' + key
, copy
.copy(value
)) 
1296             if not kwargs
.has_key(key
): 
1297                 kwargs
[key
] = copy
.copy(value
) 
1299         self
._autoCompleteIndex 
= -1 
1300         self
._SetParameters
(**kwargs
) 
1301         self
._ValidateParameters
(**kwargs
) 
1306     def _SetParameters(self
, **kwargs
): 
1308         This function can be used to set individual or multiple parameters for 
1309         a masked edit field parameter after construction. 
1312 ##        dbg('maskededit.Field::_SetParameters', indent=1) 
1313         # Validate keyword arguments: 
1314         for key 
in kwargs
.keys(): 
1315             if key 
not in Field
.valid_params
.keys(): 
1316 ##                dbg(indent=0, suspend=0) 
1317                 raise AttributeError('invalid keyword argument "%s"' % key
) 
1319 ##        if self._index is not None: dbg('field index:', self._index) 
1320 ##        dbg('parameters:', indent=1) 
1321         for key
, value 
in kwargs
.items(): 
1322 ##            dbg('%s:' % key, value) 
1327         old_fillChar 
= self
._fillChar   
# store so we can change choice lists accordingly if it changes 
1329         # First, Assign all parameters specified: 
1330         for key 
in Field
.valid_params
.keys(): 
1331             if kwargs
.has_key(key
): 
1332                 setattr(self
, '_' + key
, kwargs
[key
] ) 
1334         if kwargs
.has_key('formatcodes'):   # (set/changed) 
1335             self
._forceupper  
= '!' in self
._formatcodes
 
1336             self
._forcelower  
= '^' in self
._formatcodes
 
1337             self
._groupdigits 
= ',' in self
._formatcodes
 
1338             self
._okSpaces    
= '_' in self
._formatcodes
 
1339             self
._padZero     
= '0' in self
._formatcodes
 
1340             self
._autofit     
= 'F' in self
._formatcodes
 
1341             self
._insertRight 
= 'r' in self
._formatcodes
 
1342             self
._allowInsert 
= '>' in self
._formatcodes
 
1343             self
._alignRight  
= 'R' in self
._formatcodes 
or 'r' in self
._formatcodes
 
1344             self
._moveOnFieldFull 
= not '<' in self
._formatcodes
 
1345             self
._selectOnFieldEntry 
= 'S' in self
._formatcodes
 
1347             if kwargs
.has_key('groupChar'): 
1348                 self
._groupChar 
= kwargs
['groupChar'] 
1349             if kwargs
.has_key('decimalChar'): 
1350                 self
._decimalChar 
= kwargs
['decimalChar'] 
1351             if kwargs
.has_key('shiftDecimalChar'): 
1352                 self
._shiftDecimalChar 
= kwargs
['shiftDecimalChar'] 
1354         if kwargs
.has_key('formatcodes') or kwargs
.has_key('validRegex'): 
1355             self
._regexMask   
= 'V' in self
._formatcodes 
and self
._validRegex
 
1357         if kwargs
.has_key('fillChar'): 
1358             self
._old
_fillChar 
= old_fillChar
 
1359 ####            dbg("self._old_fillChar: '%s'" % self._old_fillChar) 
1361         if kwargs
.has_key('mask') or kwargs
.has_key('validRegex'):  # (set/changed) 
1362             self
._isInt 
= _isInteger(self
._mask
) 
1363 ##            dbg('isInt?', self._isInt, 'self._mask:"%s"' % self._mask) 
1365 ##        dbg(indent=0, suspend=0) 
1368     def _ValidateParameters(self
, **kwargs
): 
1370         This function can be used to validate individual or multiple parameters for 
1371         a masked edit field parameter after construction. 
1374 ##        dbg('maskededit.Field::_ValidateParameters', indent=1) 
1375 ##        if self._index is not None: dbg('field index:', self._index) 
1376 ####        dbg('parameters:', indent=1) 
1377 ##        for key, value in kwargs.items(): 
1378 ####            dbg('%s:' % key, value) 
1380 ####        dbg("self._old_fillChar: '%s'" % self._old_fillChar) 
1382         # Verify proper numeric format params: 
1383         if self
._groupdigits 
and self
._groupChar 
== self
._decimalChar
: 
1384 ##            dbg(indent=0, suspend=0) 
1385             raise AttributeError("groupChar '%s' cannot be the same as decimalChar '%s'" % (self
._groupChar
, self
._decimalChar
)) 
1388         # Now go do validation, semantic and inter-dependency parameter processing: 
1389         if kwargs
.has_key('choices') or kwargs
.has_key('compareNoCase') or kwargs
.has_key('choiceRequired'): # (set/changed) 
1391             self
._compareChoices 
= [choice
.strip() for choice 
in self
._choices
] 
1393             if self
._compareNoCase 
and self
._choices
: 
1394                 self
._compareChoices 
= [item
.lower() for item 
in self
._compareChoices
] 
1396             if kwargs
.has_key('choices'): 
1397                 self
._autoCompleteIndex 
= -1 
1400         if kwargs
.has_key('validRegex'):    # (set/changed) 
1401             if self
._validRegex
: 
1403                     if self
._compareNoCase
: 
1404                         self
._filter 
= re
.compile(self
._validRegex
, re
.IGNORECASE
) 
1406                         self
._filter 
= re
.compile(self
._validRegex
) 
1408 ##                    dbg(indent=0, suspend=0) 
1409                     raise TypeError('%s: validRegex "%s" not a legal regular expression' % (str(self
._index
), self
._validRegex
)) 
1413         if kwargs
.has_key('validRange'):    # (set/changed) 
1414             self
._hasRange  
= False 
1417             if self
._validRange
: 
1418                 if type(self
._validRange
) != types
.TupleType 
or len( self
._validRange 
)!= 2 or self
._validRange
[0] > self
._validRange
[1]: 
1419 ##                    dbg(indent=0, suspend=0) 
1420                     raise TypeError('%s: validRange %s parameter must be tuple of form (a,b) where a <= b' 
1421                                     % (str(self
._index
), repr(self
._validRange
)) ) 
1423                 self
._hasRange  
= True 
1424                 self
._rangeLow  
= self
._validRange
[0] 
1425                 self
._rangeHigh 
= self
._validRange
[1] 
1427         if kwargs
.has_key('choices') or (len(self
._choices
) and len(self
._choices
[0]) != len(self
._mask
)):       # (set/changed) 
1428             self
._hasList   
= False 
1429             if self
._choices 
and type(self
._choices
) not in (types
.TupleType
, types
.ListType
): 
1430 ##                dbg(indent=0, suspend=0) 
1431                 raise TypeError('%s: choices must be a sequence of strings' % str(self
._index
)) 
1432             elif len( self
._choices
) > 0: 
1433                 for choice 
in self
._choices
: 
1434                     if type(choice
) not in (types
.StringType
, types
.UnicodeType
): 
1435 ##                        dbg(indent=0, suspend=0) 
1436                         raise TypeError('%s: choices must be a sequence of strings' % str(self
._index
)) 
1438                 length 
= len(self
._mask
) 
1439 ##                dbg('len(%s)' % self._mask, length, 'len(self._choices):', len(self._choices), 'length:', length, 'self._alignRight?', self._alignRight) 
1440                 if len(self
._choices
) and length
: 
1441                     if len(self
._choices
[0]) > length
: 
1442                         # changed mask without respecifying choices; readjust the width as appropriate: 
1443                         self
._choices 
= [choice
.strip() for choice 
in self
._choices
] 
1444                     if self
._alignRight
: 
1445                         self
._choices 
= [choice
.rjust( length 
) for choice 
in self
._choices
] 
1447                         self
._choices 
= [choice
.ljust( length 
) for choice 
in self
._choices
] 
1448 ##                    dbg('aligned choices:', self._choices) 
1450                 if hasattr(self
, '_template'): 
1451                     # Verify each choice specified is valid: 
1452                     for choice 
in self
._choices
: 
1453                         if self
.IsEmpty(choice
) and not self
._validRequired
: 
1454                             # allow empty values even if invalid, (just colored differently) 
1456                         if not self
.IsValid(choice
): 
1457 ##                            dbg(indent=0, suspend=0) 
1458                             raise ValueError('%s: "%s" is not a valid value for the control as specified.' % (str(self
._index
), choice
)) 
1459                 self
._hasList 
= True 
1461 ####        dbg("kwargs.has_key('fillChar')?", kwargs.has_key('fillChar'), "len(self._choices) > 0?", len(self._choices) > 0) 
1462 ####        dbg("self._old_fillChar:'%s'" % self._old_fillChar, "self._fillChar: '%s'" % self._fillChar) 
1463         if kwargs
.has_key('fillChar') and len(self
._choices
) > 0: 
1464             if kwargs
['fillChar'] != ' ': 
1465                 self
._choices 
= [choice
.replace(' ', self
._fillChar
) for choice 
in self
._choices
] 
1467                 self
._choices 
= [choice
.replace(self
._old
_fillChar
, self
._fillChar
) for choice 
in self
._choices
] 
1468 ##            dbg('updated choices:', self._choices) 
1471         if kwargs
.has_key('autoSelect') and kwargs
['autoSelect']: 
1472             if not self
._hasList
: 
1473 ##                dbg('no list to auto complete; ignoring "autoSelect=True"') 
1474                 self
._autoSelect 
= False 
1476         # reset field validity assumption: 
1478 ##        dbg(indent=0, suspend=0) 
1481     def _GetParameter(self
, paramname
): 
1483         Routine for retrieving the value of any given parameter 
1485         if Field
.valid_params
.has_key(paramname
): 
1486             return getattr(self
, '_' + paramname
) 
1488             TypeError('Field._GetParameter: invalid parameter "%s"' % key
) 
1491     def IsEmpty(self
, slice): 
1493         Indicates whether the specified slice is considered empty for the 
1496 ##        dbg('Field::IsEmpty("%s")' % slice, indent=1) 
1497         if not hasattr(self
, '_template'): 
1499             raise AttributeError('_template') 
1501 ##        dbg('self._template: "%s"' % self._template) 
1502 ##        dbg('self._defaultValue: "%s"' % str(self._defaultValue)) 
1503         if slice == self
._template 
and not self
._defaultValue
: 
1507         elif slice == self
._template
: 
1509             for pos 
in range(len(self
._template
)): 
1510 ####                dbg('slice[%(pos)d] != self._fillChar?' %locals(), slice[pos] != self._fillChar[pos]) 
1511                 if slice[pos
] not in (' ', self
._fillChar
): 
1514 ##            dbg("IsEmpty? %(empty)d (do all mask chars == fillChar?)" % locals(), indent=0) 
1517 ##            dbg("IsEmpty? 0 (slice doesn't match template)", indent=0) 
1521     def IsValid(self
, slice): 
1523         Indicates whether the specified slice is considered a valid value for the 
1527 ##        dbg('Field[%s]::IsValid("%s")' % (str(self._index), slice), indent=1) 
1528         valid 
= True    # assume true to start 
1530         if self
.IsEmpty(slice): 
1531 ##            dbg(indent=0, suspend=0) 
1532             if self
._emptyInvalid
: 
1537         elif self
._hasList 
and self
._choiceRequired
: 
1538 ##            dbg("(member of list required)") 
1539             # do case-insensitive match on list; strip surrounding whitespace from slice (already done for choices): 
1540             if self
._fillChar 
!= ' ': 
1541                 slice = slice.replace(self
._fillChar
, ' ') 
1542 ##                dbg('updated slice:"%s"' % slice) 
1543             compareStr 
= slice.strip() 
1545             if self
._compareNoCase
: 
1546                 compareStr 
= compareStr
.lower() 
1547             valid 
= compareStr 
in self
._compareChoices
 
1549         elif self
._hasRange 
and not self
.IsEmpty(slice): 
1550 ##            dbg('validating against range') 
1552                 # allow float as well as int ranges (int comparisons for free.) 
1553                 valid 
= self
._rangeLow 
<= float(slice) <= self
._rangeHigh
 
1557         elif self
._validRegex 
and self
._filter
: 
1558 ##            dbg('validating against regex') 
1559             valid 
= (re
.match( self
._filter
, slice) is not None) 
1561         if valid 
and self
._validFunc
: 
1562 ##            dbg('validating against supplied function') 
1563             valid 
= self
._validFunc
(slice) 
1564 ##        dbg('valid?', valid, indent=0, suspend=0) 
1568     def _AdjustField(self
, slice): 
1569         """ 'Fixes' an integer field. Right or left-justifies, as required.""" 
1570 ##        dbg('Field::_AdjustField("%s")' % slice, indent=1) 
1571         length 
= len(self
._mask
) 
1572 ####        dbg('length(self._mask):', length) 
1573 ####        dbg('self._useParensForNegatives?', self._useParensForNegatives) 
1575             if self
._useParensForNegatives
: 
1576                 signpos 
= slice.find('(') 
1577                 right_signpos 
= slice.find(')') 
1578                 intStr 
= slice.replace('(', '').replace(')', '')    # drop sign, if any 
1580                 signpos 
= slice.find('-') 
1581                 intStr 
= slice.replace( '-', '' )                   # drop sign, if any 
1584             intStr 
= intStr
.replace(' ', '')                        # drop extra spaces 
1585             intStr 
= string
.replace(intStr
,self
._fillChar
,"")       # drop extra fillchars 
1586             intStr 
= string
.replace(intStr
,"-","")                  # drop sign, if any 
1587             intStr 
= string
.replace(intStr
, self
._groupChar
, "")    # lose commas/dots 
1588 ####            dbg('intStr:"%s"' % intStr) 
1589             start
, end 
= self
._extent
 
1590             field_len 
= end 
- start
 
1591             if not self
._padZero 
and len(intStr
) != field_len 
and intStr
.strip(): 
1592                 intStr 
= str(long(intStr
)) 
1593 ####            dbg('raw int str: "%s"' % intStr) 
1594 ####            dbg('self._groupdigits:', self._groupdigits, 'self._formatcodes:', self._formatcodes) 
1595             if self
._groupdigits
: 
1598                 for i 
in range(len(intStr
)-1, -1, -1): 
1599                     new 
= intStr
[i
] + new
 
1601                         new 
= self
._groupChar 
+ new
 
1603                 if new 
and new
[0] == self
._groupChar
: 
1605                 if len(new
) <= length
: 
1606                     # expanded string will still fit and leave room for sign: 
1608                 # else... leave it without the commas... 
1610 ##            dbg('padzero?', self._padZero) 
1611 ##            dbg('len(intStr):', len(intStr), 'field length:', length) 
1612             if self
._padZero 
and len(intStr
) < length
: 
1613                 intStr 
= '0' * (length 
- len(intStr
)) + intStr
 
1614                 if signpos 
!= -1:   # we had a sign before; restore it 
1615                     if self
._useParensForNegatives
: 
1616                         intStr 
= '(' + intStr
[1:] 
1617                         if right_signpos 
!= -1: 
1620                         intStr 
= '-' + intStr
[1:] 
1621             elif signpos 
!= -1 and slice[0:signpos
].strip() == '':    # - was before digits 
1622                 if self
._useParensForNegatives
: 
1623                     intStr 
= '(' + intStr
 
1624                     if right_signpos 
!= -1: 
1627                     intStr 
= '-' + intStr
 
1628             elif right_signpos 
!= -1: 
1629                 # must have had ')' but '(' was before field; re-add ')' 
1633         slice = slice.strip() # drop extra spaces 
1635         if self
._alignRight
:     ## Only if right-alignment is enabled 
1636             slice = slice.rjust( length 
) 
1638             slice = slice.ljust( length 
) 
1639         if self
._fillChar 
!= ' ': 
1640             slice = slice.replace(' ', self
._fillChar
) 
1641 ##        dbg('adjusted slice: "%s"' % slice, indent=0) 
1645 ## ---------- ---------- ---------- ---------- ---------- ---------- ---------- 
1647 class MaskedEditMixin
: 
1649     This class allows us to abstract the masked edit functionality that could 
1650     be associated with any text entry control. (eg. wx.TextCtrl, wx.ComboBox, etc.) 
1651     It forms the basis for all of the lib.masked controls. 
1653     valid_ctrl_params 
= { 
1654               'mask': 'XXXXXXXXXXXXX',          ## mask string for formatting this control 
1655               'autoformat':   "",               ## optional auto-format code to set format from masktags dictionary 
1656               'fields': {},                     ## optional list/dictionary of maskededit.Field class instances, indexed by position in mask 
1657               'datestyle':    'MDY',            ## optional date style for date-type values. Can trigger autocomplete year 
1658               'autoCompleteKeycodes': [],       ## Optional list of additional keycodes which will invoke field-auto-complete 
1659               'useFixedWidthFont': True,        ## Use fixed-width font instead of default for base control 
1660               'defaultEncoding': 'latin1',      ## optional argument to indicate unicode codec to use (unicode ctrls only) 
1661               'retainFieldValidation': False,   ## Set this to true if setting control-level parameters independently, 
1662                                                 ## from field validation constraints 
1663               'emptyBackgroundColour': "White", 
1664               'validBackgroundColour': "White", 
1665               'invalidBackgroundColour': "Yellow", 
1666               'foregroundColour': "Black", 
1667               'signedForegroundColour': "Red", 
1671     def __init__(self
, name 
= 'MaskedEdit', **kwargs
): 
1673         This is the "constructor" for setting up the mixin variable parameters for the composite class. 
1678         # set up flag for doing optional things to base control if possible 
1679         if not hasattr(self
, 'controlInitialized'): 
1680             self
.controlInitialized 
= False 
1682         # Set internal state var for keeping track of whether or not a character 
1683         # action results in a modification of the control, since .SetValue() 
1684         # doesn't modify the base control's internal state: 
1685         self
.modified 
= False 
1686         self
._previous
_mask 
= None 
1688         # Validate legitimate set of parameters: 
1689         for key 
in kwargs
.keys(): 
1690             if key
.replace('Color', 'Colour') not in MaskedEditMixin
.valid_ctrl_params
.keys() + Field
.valid_params
.keys(): 
1691                 raise TypeError('%s: invalid parameter "%s"' % (name
, key
)) 
1693         ## Set up dictionary that can be used by subclasses to override or add to default 
1694         ## behavior for individual characters.  Derived subclasses needing to change 
1695         ## default behavior for keys can either redefine the default functions for the 
1696         ## common keys or add functions for specific keys to this list.  Each function 
1697         ## added should take the key event as argument, and return False if the key 
1698         ## requires no further processing. 
1700         ## Initially populated with navigation and function control keys: 
1701         self
._keyhandlers 
= { 
1702             # default navigation keys and handlers: 
1703             wx
.WXK_BACK
:   self
._OnErase
, 
1704             wx
.WXK_LEFT
:   self
._OnArrow
, 
1705             wx
.WXK_RIGHT
:  self
._OnArrow
, 
1706             wx
.WXK_UP
:     self
._OnAutoCompleteField
, 
1707             wx
.WXK_DOWN
:   self
._OnAutoCompleteField
, 
1708             wx
.WXK_TAB
:    self
._OnChangeField
, 
1709             wx
.WXK_HOME
:   self
._OnHome
, 
1710             wx
.WXK_END
:    self
._OnEnd
, 
1711             wx
.WXK_RETURN
: self
._OnReturn
, 
1712             wx
.WXK_PRIOR
:  self
._OnAutoCompleteField
, 
1713             wx
.WXK_NEXT
:   self
._OnAutoCompleteField
, 
1715             # default function control keys and handlers: 
1716             wx
.WXK_DELETE
: self
._OnErase
, 
1717             WXK_CTRL_A
: self
._OnCtrl
_A
, 
1718             WXK_CTRL_C
: self
._OnCtrl
_C
, 
1719             WXK_CTRL_S
: self
._OnCtrl
_S
, 
1720             WXK_CTRL_V
: self
._OnCtrl
_V
, 
1721             WXK_CTRL_X
: self
._OnCtrl
_X
, 
1722             WXK_CTRL_Z
: self
._OnCtrl
_Z
, 
1725         ## bind standard navigational and control keycodes to this instance, 
1726         ## so that they can be augmented and/or changed in derived classes: 
1727         self
._nav 
= list(nav
) 
1728         self
._control 
= list(control
) 
1730         ## Dynamically evaluate and store string constants for mask chars 
1731         ## so that locale settings can be made after this module is imported 
1732         ## and the controls created after that is done can allow the 
1733         ## appropriate characters: 
1734         self
.maskchardict  
= { 
1736             'A': string
.uppercase
, 
1737             'a': string
.lowercase
, 
1738             'X': string
.letters 
+ string
.punctuation 
+ string
.digits
, 
1739             'C': string
.letters
, 
1740             'N': string
.letters 
+ string
.digits
, 
1741             '&': string
.punctuation
, 
1745         ## self._ignoreChange is used by MaskedComboBox, because 
1746         ## of the hack necessary to determine the selection; it causes 
1747         ## EVT_TEXT messages from the combobox to be ignored if set. 
1748         self
._ignoreChange 
= False 
1750         # These are used to keep track of previous value, for undo functionality: 
1751         self
._curValue  
= None 
1752         self
._prevValue 
= None 
1756         # Set defaults for each parameter for this instance, and fully 
1757         # populate initial parameter list for configuration: 
1758         for key
, value 
in MaskedEditMixin
.valid_ctrl_params
.items(): 
1759             setattr(self
, '_' + key
, copy
.copy(value
)) 
1760             if not kwargs
.has_key(key
): 
1761 ####                dbg('%s: "%s"' % (key, repr(value))) 
1762                 kwargs
[key
] = copy
.copy(value
) 
1764         # Create a "field" that holds global parameters for control constraints 
1765         self
._ctrl
_constraints 
= self
._fields
[-1] = Field(index
=-1) 
1766         self
.SetCtrlParameters(**kwargs
) 
1770     def SetCtrlParameters(self
, **kwargs
): 
1772         This public function can be used to set individual or multiple masked edit 
1773         parameters after construction.  (See maskededit module overview for the list 
1774         of valid parameters.) 
1777 ##        dbg('MaskedEditMixin::SetCtrlParameters', indent=1) 
1778 ####        dbg('kwargs:', indent=1) 
1779 ##        for key, value in kwargs.items(): 
1780 ####            dbg(key, '=', value) 
1783         # Validate keyword arguments: 
1784         constraint_kwargs 
= {} 
1786         for key
, value 
in kwargs
.items(): 
1787             key 
= key
.replace('Color', 'Colour')    # for b-c, and standard wxPython spelling 
1788             if key 
not in MaskedEditMixin
.valid_ctrl_params
.keys() + Field
.valid_params
.keys(): 
1789 ##                dbg(indent=0, suspend=0) 
1790                 raise TypeError('Invalid keyword argument "%s" for control "%s"' % (key
, self
.name
)) 
1791             elif key 
in Field
.valid_params
.keys(): 
1792                 constraint_kwargs
[key
] = value
 
1794                 ctrl_kwargs
[key
] = value
 
1799         if ctrl_kwargs
.has_key('autoformat'): 
1800             autoformat 
= ctrl_kwargs
['autoformat'] 
1804         # handle "parochial name" backward compatibility: 
1805         if autoformat 
and autoformat
.find('MILTIME') != -1 and autoformat 
not in masktags
.keys(): 
1806             autoformat 
= autoformat
.replace('MILTIME', '24HRTIME') 
1808         if autoformat 
!= self
._autoformat 
and autoformat 
in masktags
.keys(): 
1809 ##            dbg('autoformat:', autoformat) 
1810             self
._autoformat                  
= autoformat
 
1811             mask                              
= masktags
[self
._autoformat
]['mask'] 
1812             # gather rest of any autoformat parameters: 
1813             for param
, value 
in masktags
[self
._autoformat
].items(): 
1814                 if param 
== 'mask': continue    # (must be present; already accounted for) 
1815                 constraint_kwargs
[param
] = value
 
1817         elif autoformat 
and not autoformat 
in masktags
.keys(): 
1818             raise AttributeError('invalid value for autoformat parameter: %s' % repr(autoformat
)) 
1820 ##            dbg('autoformat not selected') 
1821             if kwargs
.has_key('mask'): 
1822                 mask 
= kwargs
['mask'] 
1823 ##                dbg('mask:', mask) 
1825         ## Assign style flags 
1827 ##            dbg('preserving previous mask') 
1828             mask 
= self
._previous
_mask   
# preserve previous mask 
1830 ##            dbg('mask (re)set') 
1831             reset_args
['reset_mask'] = mask
 
1832             constraint_kwargs
['mask'] = mask
 
1834             # wipe out previous fields; preserve new control-level constraints 
1835             self
._fields 
= {-1: self._ctrl_constraints}
 
1838         if ctrl_kwargs
.has_key('fields'): 
1839             # do field parameter type validation, and conversion to internal dictionary 
1841             fields 
= ctrl_kwargs
['fields'] 
1842             if type(fields
) in (types
.ListType
, types
.TupleType
): 
1843                 for i 
in range(len(fields
)): 
1845                     if not isinstance(field
, Field
): 
1846 ##                        dbg(indent=0, suspend=0) 
1847                         raise AttributeError('invalid type for field parameter: %s' % repr(field
)) 
1848                     self
._fields
[i
] = field
 
1850             elif type(fields
) == types
.DictionaryType
: 
1851                 for index
, field 
in fields
.items(): 
1852                     if not isinstance(field
, Field
): 
1853 ##                        dbg(indent=0, suspend=0) 
1854                         raise AttributeError('invalid type for field parameter: %s' % repr(field
)) 
1855                     self
._fields
[index
] = field
 
1857 ##                dbg(indent=0, suspend=0) 
1858                 raise AttributeError('fields parameter must be a list or dictionary; not %s' % repr(fields
)) 
1860         # Assign constraint parameters for entire control: 
1861 ####        dbg('control constraints:', indent=1) 
1862 ##        for key, value in constraint_kwargs.items(): 
1863 ####            dbg('%s:' % key, value) 
1866         # determine if changing parameters that should affect the entire control: 
1867         for key 
in MaskedEditMixin
.valid_ctrl_params
.keys(): 
1868             if key 
in ( 'mask', 'fields' ): continue    # (processed separately) 
1869             if ctrl_kwargs
.has_key(key
): 
1870                 setattr(self
, '_' + key
, ctrl_kwargs
[key
]) 
1872         # Validate color parameters, converting strings to named colors and validating 
1873         # result if appropriate: 
1874         for key 
in ('emptyBackgroundColour', 'invalidBackgroundColour', 'validBackgroundColour', 
1875                     'foregroundColour', 'signedForegroundColour'): 
1876             if ctrl_kwargs
.has_key(key
): 
1877                 if type(ctrl_kwargs
[key
]) in (types
.StringType
, types
.UnicodeType
): 
1878                     c 
= wx
.NamedColour(ctrl_kwargs
[key
]) 
1879                     if c
.Get() == (-1, -1, -1): 
1880                         raise TypeError('%s not a legal color specification for %s' % (repr(ctrl_kwargs
[key
]), key
)) 
1882                         # replace attribute with wxColour object: 
1883                         setattr(self
, '_' + key
, c
) 
1884                         # attach a python dynamic attribute to wxColour for debug printouts 
1885                         c
._name 
= ctrl_kwargs
[key
] 
1887                 elif type(ctrl_kwargs
[key
]) != type(wx
.BLACK
): 
1888                     raise TypeError('%s not a legal color specification for %s' % (repr(ctrl_kwargs
[key
]), key
)) 
1891 ##        dbg('self._retainFieldValidation:', self._retainFieldValidation) 
1892         if not self
._retainFieldValidation
: 
1893             # Build dictionary of any changing parameters which should be propagated to the 
1895             for arg 
in Field
.propagating_params
: 
1896 ####                dbg('kwargs.has_key(%s)?' % arg, kwargs.has_key(arg)) 
1897 ####                dbg('getattr(self._ctrl_constraints, _%s)?' % arg, getattr(self._ctrl_constraints, '_'+arg)) 
1898                 reset_args
[arg
] = kwargs
.has_key(arg
) and kwargs
[arg
] != getattr(self
._ctrl
_constraints
, '_'+arg
) 
1899 ####                dbg('reset_args[%s]?' % arg, reset_args[arg]) 
1901         # Set the control-level constraints: 
1902         self
._ctrl
_constraints
._SetParameters
(**constraint_kwargs
) 
1904         # This routine does the bulk of the interdependent parameter processing, determining 
1905         # the field extents of the mask if changed, resetting parameters as appropriate, 
1906         # determining the overall template value for the control, etc. 
1907         self
._configure
(mask
, **reset_args
) 
1909         # now that we've propagated the field constraints and mask portions to the 
1910         # various fields, validate the constraints 
1911         self
._ctrl
_constraints
._ValidateParameters
(**constraint_kwargs
) 
1913         # Validate that all choices for given fields are at least of the 
1914         # necessary length, and that they all would be valid pastes if pasted 
1915         # into their respective fields: 
1916 ####        dbg('validating choices') 
1917         self
._validateChoices
() 
1920         self
._autofit 
= self
._ctrl
_constraints
._autofit
 
1923         self
._isDate     
= 'D' in self
._ctrl
_constraints
._formatcodes 
and _isDateType(mask
) 
1924         self
._isTime     
= 'T' in self
._ctrl
_constraints
._formatcodes 
and _isTimeType(mask
) 
1926             # Set _dateExtent, used in date validation to locate date in string; 
1927             # always set as though year will be 4 digits, even if mask only has 
1928             # 2 digits, so we can always properly process the intended year for 
1929             # date validation (leap years, etc.) 
1930             if self
._mask
.find('CCC') != -1: self
._dateExtent 
= 11 
1931             else:                            self
._dateExtent 
= 10 
1933             self
._4digityear 
= len(self
._mask
) > 8 and self
._mask
[9] == '#' 
1935         if self
._isDate 
and self
._autoformat
: 
1936             # Auto-decide datestyle: 
1937             if self
._autoformat
.find('MDDY')    != -1: self
._datestyle 
= 'MDY' 
1938             elif self
._autoformat
.find('YMMD')  != -1: self
._datestyle 
= 'YMD' 
1939             elif self
._autoformat
.find('YMMMD') != -1: self
._datestyle 
= 'YMD' 
1940             elif self
._autoformat
.find('DMMY')  != -1: self
._datestyle 
= 'DMY' 
1941             elif self
._autoformat
.find('DMMMY') != -1: self
._datestyle 
= 'DMY' 
1943         # Give derived controls a chance to react to parameter changes before 
1944         # potentially changing current value of the control. 
1945         self
._OnCtrlParametersChanged
() 
1947         if self
.controlInitialized
: 
1948             # Then the base control is available for configuration; 
1949             # take action on base control based on new settings, as appropriate. 
1950             if kwargs
.has_key('useFixedWidthFont'): 
1951                 # Set control font - fixed width by default 
1954             if reset_args
.has_key('reset_mask'): 
1955 ##                dbg('reset mask') 
1956                 curvalue 
= self
._GetValue
() 
1957                 if curvalue
.strip(): 
1959 ##                        dbg('attempting to _SetInitialValue(%s)' % self._GetValue()) 
1960                         self
._SetInitialValue
(self
._GetValue
()) 
1961                     except Exception, e
: 
1962 ##                        dbg('exception caught:', e) 
1963 ##                        dbg("current value doesn't work; attempting to reset to template") 
1964                         self
._SetInitialValue
() 
1966 ##                    dbg('attempting to _SetInitialValue() with template') 
1967                     self
._SetInitialValue
() 
1969             elif kwargs
.has_key('useParensForNegatives'): 
1970                 newvalue 
= self
._getSignedValue
()[0] 
1972                 if newvalue 
is not None: 
1973                     # Adjust for new mask: 
1974                     if len(newvalue
) < len(self
._mask
): 
1976                     elif len(newvalue
) > len(self
._mask
): 
1977                         if newvalue
[-1] in (' ', ')'): 
1978                             newvalue 
= newvalue
[:-1] 
1980 ##                    dbg('reconfiguring value for parens:"%s"' % newvalue) 
1981                     self
._SetValue
(newvalue
) 
1983                     if self
._prevValue 
!= newvalue
: 
1984                         self
._prevValue 
= newvalue  
# disallow undo of sign type 
1987 ##                dbg('calculated size:', self._CalcSize()) 
1988                 self
.SetClientSize(self
._CalcSize
()) 
1989                 width 
= self
.GetSize().width
 
1990                 height 
= self
.GetBestSize().height
 
1991 ##                dbg('setting client size to:', (width, height)) 
1992                 self
.SetBestFittingSize((width
, height
)) 
1994             # Set value/type-specific formatting 
1995             self
._applyFormatting
() 
1996 ##        dbg(indent=0, suspend=0) 
1998     def SetMaskParameters(self
, **kwargs
): 
1999         """ old name for the SetCtrlParameters function  (DEPRECATED)""" 
2000         return self
.SetCtrlParameters(**kwargs
) 
2003     def GetCtrlParameter(self
, paramname
): 
2005         Routine for retrieving the value of any given parameter 
2007         if MaskedEditMixin
.valid_ctrl_params
.has_key(paramname
.replace('Color','Colour')): 
2008             return getattr(self
, '_' + paramname
.replace('Color', 'Colour')) 
2009         elif Field
.valid_params
.has_key(paramname
): 
2010             return self
._ctrl
_constraints
._GetParameter
(paramname
) 
2012             TypeError('"%s".GetCtrlParameter: invalid parameter "%s"' % (self
.name
, paramname
)) 
2014     def GetMaskParameter(self
, paramname
): 
2015         """ old name for the GetCtrlParameters function  (DEPRECATED)""" 
2016         return self
.GetCtrlParameter(paramname
) 
2019 ## This idea worked, but Boa was unable to use this solution... 
2020 ##    def _attachMethod(self, func): 
2022 ##        setattr(self, func.__name__, new.instancemethod(func, self, self.__class__)) 
2025 ##    def _DefinePropertyFunctions(exposed_params): 
2026 ##        for param in exposed_params: 
2027 ##            propname = param[0].upper() + param[1:] 
2029 ##            exec('def Set%s(self, value): self.SetCtrlParameters(%s=value)' % (propname, param)) 
2030 ##            exec('def Get%s(self): return self.GetCtrlParameter("%s")''' % (propname, param)) 
2031 ##            self._attachMethod(locals()['Set%s' % propname]) 
2032 ##            self._attachMethod(locals()['Get%s' % propname]) 
2034 ##            if param.find('Colour') != -1: 
2035 ##                # add non-british spellings, for backward-compatibility 
2036 ##                propname.replace('Colour', 'Color') 
2038 ##                exec('def Set%s(self, value): self.SetCtrlParameters(%s=value)' % (propname, param)) 
2039 ##                exec('def Get%s(self): return self.GetCtrlParameter("%s")''' % (propname, param)) 
2040 ##                self._attachMethod(locals()['Set%s' % propname]) 
2041 ##                self._attachMethod(locals()['Get%s' % propname]) 
2045     def SetFieldParameters(self
, field_index
, **kwargs
): 
2047         Routine provided to modify the parameters of a given field. 
2048         Because changes to fields can affect the overall control, 
2049         direct access to the fields is prevented, and the control 
2050         is always "reconfigured" after setting a field parameter. 
2051         (See maskededit module overview for the list of valid field-level 
2054         if field_index 
not in self
._field
_indices
: 
2055             raise IndexError('%s is not a valid field for control "%s".' % (str(field_index
), self
.name
)) 
2056         # set parameters as requested: 
2057         self
._fields
[field_index
]._SetParameters
(**kwargs
) 
2059         # Possibly reprogram control template due to resulting changes, and ensure 
2060         # control-level params are still propagated to fields: 
2061         self
._configure
(self
._previous
_mask
) 
2062         self
._fields
[field_index
]._ValidateParameters
(**kwargs
) 
2064         if self
.controlInitialized
: 
2065             if kwargs
.has_key('fillChar') or kwargs
.has_key('defaultValue'): 
2066                 self
._SetInitialValue
() 
2069                     # this is tricky, because, as Robin explains: 
2070                     # "Basically there are two sizes to deal with, that are potentially  
2071                     #  different.  The client size is the inside size and may, depending 
2072                     #  on platform, exclude the borders and such.  The normal size is 
2073                     #  the outside size that does include the borders.  What you are 
2074                     #  calculating (in _CalcSize) is the client size, but the sizers 
2075                     #  deal with the full size and so that is the minimum size that 
2076                     #  we need to set with SetBestFittingSize.  The root of the problem is 
2077                     #  that in _calcSize the current client size height is returned, 
2078                     #  instead of a height based on the current font.  So I suggest using 
2079                     #  _calcSize to just get the width, and then use GetBestSize to 
2081                     self
.SetClientSize(self
._CalcSize
()) 
2082                     width 
= self
.GetSize().width
 
2083                     height 
= self
.GetBestSize().height
 
2084                     self
.SetBestFittingSize((width
, height
)) 
2087             # Set value/type-specific formatting 
2088             self
._applyFormatting
() 
2091     def GetFieldParameter(self
, field_index
, paramname
): 
2093         Routine provided for getting a parameter of an individual field. 
2095         if field_index 
not in self
._field
_indices
: 
2096             raise IndexError('%s is not a valid field for control "%s".' % (str(field_index
), self
.name
)) 
2097         elif Field
.valid_params
.has_key(paramname
): 
2098             return self
._fields
[field_index
]._GetParameter
(paramname
) 
2100             TypeError('"%s".GetFieldParameter: invalid parameter "%s"' % (self
.name
, paramname
)) 
2103     def _SetKeycodeHandler(self
, keycode
, func
): 
2105         This function adds and/or replaces key event handling functions 
2106         used by the control.  <func> should take the event as argument 
2107         and return False if no further action on the key is necessary. 
2109         self
._keyhandlers
[keycode
] = func
 
2112     def _SetKeyHandler(self
, char
, func
): 
2114         This function adds and/or replaces key event handling functions 
2115         for ascii characters.  <func> should take the event as argument 
2116         and return False if no further action on the key is necessary. 
2118         self
._SetKeycodeHandler
(ord(char
), func
) 
2121     def _AddNavKeycode(self
, keycode
, handler
=None): 
2123         This function allows a derived subclass to augment the list of 
2124         keycodes that are considered "navigational" keys. 
2126         self
._nav
.append(keycode
) 
2128             self
._keyhandlers
[keycode
] = handler
 
2131     def _AddNavKey(self
, char
, handler
=None): 
2133         This function is a convenience function so you don't have to 
2134         remember to call ord() for ascii chars to be used for navigation. 
2136         self
._AddNavKeycode
(ord(char
), handler
) 
2139     def _GetNavKeycodes(self
): 
2141         This function retrieves the current list of navigational keycodes for 
2147     def _SetNavKeycodes(self
, keycode_func_tuples
): 
2149         This function allows you to replace the current list of keycode processed 
2150         as navigation keys, and bind associated optional keyhandlers. 
2153         for keycode
, func 
in keycode_func_tuples
: 
2154             self
._nav
.append(keycode
) 
2156                 self
._keyhandlers
[keycode
] = func
 
2159     def _processMask(self
, mask
): 
2161         This subroutine expands {n} syntax in mask strings, and looks for escaped 
2162         special characters and returns the expanded mask, and an dictionary 
2163         of booleans indicating whether or not a given position in the mask is 
2164         a mask character or not. 
2166 ##        dbg('_processMask: mask', mask, indent=1) 
2167         # regular expression for parsing c{n} syntax: 
2168         rex 
= re
.compile('([' +string
.join(maskchars
,"") + '])\{(\d+)\}') 
2170         match 
= rex
.search(s
) 
2171         while match
:    # found an(other) occurrence 
2172             maskchr 
= s
[match
.start(1):match
.end(1)]            # char to be repeated 
2173             repcount 
= int(s
[match
.start(2):match
.end(2)])      # the number of times 
2174             replacement 
= string
.join( maskchr 
* repcount
, "")  # the resulting substr 
2175             s 
= s
[:match
.start(1)] + replacement 
+ s
[match
.end(2)+1:]   #account for trailing '}' 
2176             match 
= rex
.search(s
)                               # look for another such entry in mask 
2178         self
._decimalChar 
= self
._ctrl
_constraints
._decimalChar
 
2179         self
._shiftDecimalChar 
= self
._ctrl
_constraints
._shiftDecimalChar
 
2181         self
._isFloat      
= _isFloatingPoint(s
) and not self
._ctrl
_constraints
._validRegex
 
2182         self
._isInt      
= _isInteger(s
) and not self
._ctrl
_constraints
._validRegex
 
2183         self
._signOk     
= '-' in self
._ctrl
_constraints
._formatcodes 
and (self
._isFloat 
or self
._isInt
) 
2184         self
._useParens  
= self
._ctrl
_constraints
._useParensForNegatives
 
2186 ####        dbg('self._signOk?', self._signOk, 'self._useParens?', self._useParens) 
2187 ####        dbg('isFloatingPoint(%s)?' % (s), _isFloatingPoint(s), 
2188 ##            'ctrl regex:', self._ctrl_constraints._validRegex) 
2190         if self
._signOk 
and s
[0] != ' ': 
2192             if self
._ctrl
_constraints
._defaultValue 
and self
._ctrl
_constraints
._defaultValue
[0] != ' ': 
2193                 self
._ctrl
_constraints
._defaultValue 
= ' ' + self
._ctrl
_constraints
._defaultValue
 
2198                 self
._ctrl
_constraints
._defaultValue 
+= ' ' 
2200         # Now, go build up a dictionary of booleans, indexed by position, 
2201         # indicating whether or not a given position is masked or not 
2205             if s
[i
] == '\\':            # if escaped character: 
2206                 ismasked
[i
] = False     #     mark position as not a mask char 
2207                 if i
+1 < len(s
):        #     if another char follows... 
2208                     s 
= s
[:i
] + s
[i
+1:] #         elide the '\' 
2209                     if i
+2 < len(s
) and s
[i
+1] == '\\': 
2210                         # if next char also a '\', char is a literal '\' 
2211                         s 
= s
[:i
] + s
[i
+1:]     # elide the 2nd '\' as well 
2212             else:                       # else if special char, mark position accordingly 
2213                 ismasked
[i
] = s
[i
] in maskchars
 
2214 ####            dbg('ismasked[%d]:' % i, ismasked[i], s) 
2215             i 
+= 1                      # increment to next char 
2216 ####        dbg('ismasked:', ismasked) 
2217 ##        dbg('new mask: "%s"' % s, indent=0) 
2222     def _calcFieldExtents(self
): 
2224         Subroutine responsible for establishing/configuring field instances with 
2225         indices and editable extents appropriate to the specified mask, and building 
2226         the lookup table mapping each position to the corresponding field. 
2228         self
._lookupField 
= {} 
2231             ## Create dictionary of positions,characters in mask 
2233             for charnum 
in range( len( self
._mask
)): 
2234                 self
.maskdict
[charnum
] = self
._mask
[charnum
:charnum
+1] 
2236             # For the current mask, create an ordered list of field extents 
2237             # and a dictionary of positions that map to field indices: 
2239             if self
._signOk
: start 
= 1 
2243                 # Skip field "discovery", and just construct a 2-field control with appropriate 
2244                 # constraints for a floating-point entry. 
2246                 # .setdefault always constructs 2nd argument even if not needed, so we do this 
2247                 # the old-fashioned way... 
2248                 if not self
._fields
.has_key(0): 
2249                     self
._fields
[0] = Field() 
2250                 if not self
._fields
.has_key(1): 
2251                     self
._fields
[1] = Field() 
2253                 self
._decimalpos 
= string
.find( self
._mask
, '.') 
2254 ##                dbg('decimal pos =', self._decimalpos) 
2256                 formatcodes 
= self
._fields
[0]._GetParameter
('formatcodes') 
2257                 if 'R' not in formatcodes
: formatcodes 
+= 'R' 
2258                 self
._fields
[0]._SetParameters
(index
=0, extent
=(start
, self
._decimalpos
), 
2259                                                mask
=self
._mask
[start
:self
._decimalpos
], formatcodes
=formatcodes
) 
2260                 end 
= len(self
._mask
) 
2261                 if self
._signOk 
and self
._useParens
: 
2263                 self
._fields
[1]._SetParameters
(index
=1, extent
=(self
._decimalpos
+1, end
), 
2264                                                mask
=self
._mask
[self
._decimalpos
+1:end
]) 
2266                 for i 
in range(self
._decimalpos
+1): 
2267                     self
._lookupField
[i
] = 0 
2269                 for i 
in range(self
._decimalpos
+1, len(self
._mask
)+1): 
2270                     self
._lookupField
[i
] = 1 
2273                 # Skip field "discovery", and just construct a 1-field control with appropriate 
2274                 # constraints for a integer entry. 
2275                 if not self
._fields
.has_key(0): 
2276                     self
._fields
[0] = Field(index
=0) 
2277                 end 
= len(self
._mask
) 
2278                 if self
._signOk 
and self
._useParens
: 
2280                 self
._fields
[0]._SetParameters
(index
=0, extent
=(start
, end
), 
2281                                                mask
=self
._mask
[start
:end
]) 
2282                 for i 
in range(len(self
._mask
)+1): 
2283                     self
._lookupField
[i
] = 0 
2285                 # generic control; parse mask to figure out where the fields are: 
2288                 i 
= self
._findNextEntry
(pos
,adjustInsert
=False)  # go to 1st entry point: 
2289                 if i 
< len(self
._mask
):   # no editable chars! 
2290                     for j 
in range(pos
, i
+1): 
2291                         self
._lookupField
[j
] = field_index
 
2292                     pos 
= i       
# figure out field for 1st editable space: 
2294                 while i 
<= len(self
._mask
): 
2295 ####                    dbg('searching: outer field loop: i = ', i) 
2296                     if self
._isMaskChar
(i
): 
2297 ####                        dbg('1st char is mask char; recording edit_start=', i) 
2299                         # Skip to end of editable part of current field: 
2300                         while i 
< len(self
._mask
) and self
._isMaskChar
(i
): 
2301                             self
._lookupField
[i
] = field_index
 
2303 ####                        dbg('edit_end =', i) 
2305                         self
._lookupField
[i
] = field_index
 
2306 ####                        dbg('self._fields.has_key(%d)?' % field_index, self._fields.has_key(field_index)) 
2307                         if not self
._fields
.has_key(field_index
): 
2308                             kwargs 
= Field
.valid_params
.copy() 
2309                             kwargs
['index'] = field_index
 
2310                             kwargs
['extent'] = (edit_start
, edit_end
) 
2311                             kwargs
['mask'] = self
._mask
[edit_start
:edit_end
] 
2312                             self
._fields
[field_index
] = Field(**kwargs
) 
2314                             self
._fields
[field_index
]._SetParameters
( 
2316                                                                 extent
=(edit_start
, edit_end
), 
2317                                                                 mask
=self
._mask
[edit_start
:edit_end
]) 
2319                     i 
= self
._findNextEntry
(pos
, adjustInsert
=False)  # go to next field: 
2321                         for j 
in range(pos
, i
+1): 
2322                             self
._lookupField
[j
] = field_index
 
2323                     if i 
>= len(self
._mask
): 
2324                         break           # if past end, we're done 
2327 ####                        dbg('next field:', field_index) 
2329         indices 
= self
._fields
.keys() 
2331         self
._field
_indices 
= indices
[1:] 
2332 ####        dbg('lookupField map:', indent=1) 
2333 ##        for i in range(len(self._mask)): 
2334 ####            dbg('pos %d:' % i, self._lookupField[i]) 
2337         # Verify that all field indices specified are valid for mask: 
2338         for index 
in self
._fields
.keys(): 
2339             if index 
not in [-1] + self
._lookupField
.values(): 
2340                 raise IndexError('field %d is not a valid field for mask "%s"' % (index
, self
._mask
)) 
2343     def _calcTemplate(self
, reset_fillchar
, reset_default
): 
2345         Subroutine for processing current fillchars and default values for 
2346         whole control and individual fields, constructing the resulting 
2347         overall template, and adjusting the current value as necessary. 
2350         if self
._ctrl
_constraints
._defaultValue
: 
2353             for field 
in self
._fields
.values(): 
2354                 if field
._defaultValue 
and not reset_default
: 
2356 ##        dbg('default set?', default_set) 
2358         # Determine overall new template for control, and keep track of previous 
2359         # values, so that current control value can be modified as appropriate: 
2360         if self
.controlInitialized
: curvalue 
= list(self
._GetValue
()) 
2361         else:                       curvalue 
= None 
2363         if hasattr(self
, '_fillChar'): old_fillchars 
= self
._fillChar
 
2364         else:                          old_fillchars 
= None 
2366         if hasattr(self
, '_template'): old_template 
= self
._template
 
2367         else:                          old_template 
= None 
2374         for field 
in self
._fields
.values(): 
2375             field
._template 
= "" 
2377         for pos 
in range(len(self
._mask
)): 
2378 ####            dbg('pos:', pos) 
2379             field 
= self
._FindField
(pos
) 
2380 ####            dbg('field:', field._index) 
2381             start
, end 
= field
._extent
 
2383             if pos 
== 0 and self
._signOk
: 
2384                 self
._template 
= ' ' # always make 1st 1st position blank, regardless of fillchar 
2385             elif self
._isFloat 
and pos 
== self
._decimalpos
: 
2386                 self
._template 
+= self
._decimalChar
 
2387             elif self
._isMaskChar
(pos
): 
2388                 if field
._fillChar 
!= self
._ctrl
_constraints
._fillChar 
and not reset_fillchar
: 
2389                     fillChar 
= field
._fillChar
 
2391                     fillChar 
= self
._ctrl
_constraints
._fillChar
 
2392                 self
._fillChar
[pos
] = fillChar
 
2394                 # Replace any current old fillchar with new one in current value; 
2395                 # if action required, set reset_value flag so we can take that action 
2396                 # after we're all done 
2397                 if self
.controlInitialized 
and old_fillchars 
and old_fillchars
.has_key(pos
) and curvalue
: 
2398                     if curvalue
[pos
] == old_fillchars
[pos
] and old_fillchars
[pos
] != fillChar
: 
2400                         curvalue
[pos
] = fillChar
 
2402                 if not field
._defaultValue 
and not self
._ctrl
_constraints
._defaultValue
: 
2403 ####                    dbg('no default value') 
2404                     self
._template 
+= fillChar
 
2405                     field
._template 
+= fillChar
 
2407                 elif field
._defaultValue 
and not reset_default
: 
2408 ####                    dbg('len(field._defaultValue):', len(field._defaultValue)) 
2409 ####                    dbg('pos-start:', pos-start) 
2410                     if len(field
._defaultValue
) > pos
-start
: 
2411 ####                        dbg('field._defaultValue[pos-start]: "%s"' % field._defaultValue[pos-start]) 
2412                         self
._template 
+= field
._defaultValue
[pos
-start
] 
2413                         field
._template 
+= field
._defaultValue
[pos
-start
] 
2415 ####                        dbg('field default not long enough; using fillChar') 
2416                         self
._template 
+= fillChar
 
2417                         field
._template 
+= fillChar
 
2419                     if len(self
._ctrl
_constraints
._defaultValue
) > pos
: 
2420 ####                        dbg('using control default') 
2421                         self
._template 
+= self
._ctrl
_constraints
._defaultValue
[pos
] 
2422                         field
._template 
+= self
._ctrl
_constraints
._defaultValue
[pos
] 
2424 ####                        dbg('ctrl default not long enough; using fillChar') 
2425                         self
._template 
+= fillChar
 
2426                         field
._template 
+= fillChar
 
2427 ####                dbg('field[%d]._template now "%s"' % (field._index, field._template)) 
2428 ####                dbg('self._template now "%s"' % self._template) 
2430                 self
._template 
+= self
._mask
[pos
] 
2432         self
._fields
[-1]._template 
= self
._template     
# (for consistency) 
2434         if curvalue
:    # had an old value, put new one back together 
2435             newvalue 
= string
.join(curvalue
, "") 
2440             self
._defaultValue 
= self
._template
 
2441 ##            dbg('self._defaultValue:', self._defaultValue) 
2442             if not self
.IsEmpty(self
._defaultValue
) and not self
.IsValid(self
._defaultValue
): 
2444                 raise ValueError('Default value of "%s" is not a valid value for control "%s"' % (self
._defaultValue
, self
.name
)) 
2446             # if no fillchar change, but old value == old template, replace it: 
2447             if newvalue 
== old_template
: 
2448                 newvalue 
= self
._template
 
2451             self
._defaultValue 
= None 
2454 ##            dbg('resetting value to: "%s"' % newvalue) 
2455             pos 
= self
._GetInsertionPoint
() 
2456             sel_start
, sel_to 
= self
._GetSelection
() 
2457             self
._SetValue
(newvalue
) 
2458             self
._SetInsertionPoint
(pos
) 
2459             self
._SetSelection
(sel_start
, sel_to
) 
2462     def _propagateConstraints(self
, **reset_args
): 
2464         Subroutine for propagating changes to control-level constraints and 
2465         formatting to the individual fields as appropriate. 
2467         parent_codes 
= self
._ctrl
_constraints
._formatcodes
 
2468         parent_includes 
= self
._ctrl
_constraints
._includeChars
 
2469         parent_excludes 
= self
._ctrl
_constraints
._excludeChars
 
2470         for i 
in self
._field
_indices
: 
2471             field 
= self
._fields
[i
] 
2473             if len(self
._field
_indices
) == 1: 
2474                 inherit_args
['formatcodes'] = parent_codes
 
2475                 inherit_args
['includeChars'] = parent_includes
 
2476                 inherit_args
['excludeChars'] = parent_excludes
 
2478                 field_codes 
= current_codes 
= field
._GetParameter
('formatcodes') 
2479                 for c 
in parent_codes
: 
2480                     if c 
not in field_codes
: field_codes 
+= c
 
2481                 if field_codes 
!= current_codes
: 
2482                     inherit_args
['formatcodes'] = field_codes
 
2484                 include_chars 
= current_includes 
= field
._GetParameter
('includeChars') 
2485                 for c 
in parent_includes
: 
2486                     if not c 
in include_chars
: include_chars 
+= c
 
2487                 if include_chars 
!= current_includes
: 
2488                     inherit_args
['includeChars'] = include_chars
 
2490                 exclude_chars 
= current_excludes 
= field
._GetParameter
('excludeChars') 
2491                 for c 
in parent_excludes
: 
2492                     if not c 
in exclude_chars
: exclude_chars 
+= c
 
2493                 if exclude_chars 
!= current_excludes
: 
2494                     inherit_args
['excludeChars'] = exclude_chars
 
2496             if reset_args
.has_key('defaultValue') and reset_args
['defaultValue']: 
2497                 inherit_args
['defaultValue'] = ""   # (reset for field) 
2499             for param 
in Field
.propagating_params
: 
2500 ####                dbg('reset_args.has_key(%s)?' % param, reset_args.has_key(param)) 
2501 ####                dbg('reset_args.has_key(%(param)s) and reset_args[%(param)s]?' % locals(), reset_args.has_key(param) and reset_args[param]) 
2502                 if reset_args
.has_key(param
): 
2503                     inherit_args
[param
] = self
.GetCtrlParameter(param
) 
2504 ####                    dbg('inherit_args[%s]' % param, inherit_args[param]) 
2507                 field
._SetParameters
(**inherit_args
) 
2508                 field
._ValidateParameters
(**inherit_args
) 
2511     def _validateChoices(self
): 
2513         Subroutine that validates that all choices for given fields are at 
2514         least of the necessary length, and that they all would be valid pastes 
2515         if pasted into their respective fields. 
2517         for field 
in self
._fields
.values(): 
2519                 index 
= field
._index
 
2520                 if len(self
._field
_indices
) == 1 and index 
== 0 and field
._choices 
== self
._ctrl
_constraints
._choices
: 
2521 ##                    dbg('skipping (duplicate) choice validation of field 0') 
2523 ####                dbg('checking for choices for field', field._index) 
2524                 start
, end 
= field
._extent
 
2525                 field_length 
= end 
- start
 
2526 ####                dbg('start, end, length:', start, end, field_length) 
2527                 for choice 
in field
._choices
: 
2528 ####                    dbg('testing "%s"' % choice) 
2529                     valid_paste
, ignore
, replace_to 
= self
._validatePaste
(choice
, start
, end
) 
2532                         raise ValueError('"%s" could not be entered into field %d of control "%s"' % (choice
, index
, self
.name
)) 
2533                     elif replace_to 
> end
: 
2535                         raise ValueError('"%s" will not fit into field %d of control "%s"' (choice
, index
, self
.name
)) 
2536 ####                    dbg(choice, 'valid in field', index) 
2539     def _configure(self
, mask
, **reset_args
): 
2541         This function sets flags for automatic styling options.  It is 
2542         called whenever a control or field-level parameter is set/changed. 
2544         This routine does the bulk of the interdependent parameter processing, determining 
2545         the field extents of the mask if changed, resetting parameters as appropriate, 
2546         determining the overall template value for the control, etc. 
2548         reset_args is supplied if called from control's .SetCtrlParameters() 
2549         routine, and indicates which if any parameters which can be 
2550         overridden by individual fields have been reset by request for the 
2555 ##        dbg('MaskedEditMixin::_configure("%s")' % mask, indent=1) 
2557         # Preprocess specified mask to expand {n} syntax, handle escaped 
2558         # mask characters, etc and build the resulting positionally keyed 
2559         # dictionary for which positions are mask vs. template characters: 
2560         self
._mask
, self
.ismasked 
= self
._processMask
(mask
) 
2561         self
._masklength 
= len(self
._mask
) 
2562 ####        dbg('processed mask:', self._mask) 
2564         # Preserve original mask specified, for subsequent reprocessing 
2565         # if parameters change. 
2566 ##        dbg('mask: "%s"' % self._mask, 'previous mask: "%s"' % self._previous_mask) 
2567         self
._previous
_mask 
= mask    
# save unexpanded mask for next time 
2568             # Set expanded mask and extent of field -1 to width of entire control: 
2569         self
._ctrl
_constraints
._SetParameters
(mask 
= self
._mask
, extent
=(0,self
._masklength
)) 
2571         # Go parse mask to determine where each field is, construct field 
2572         # instances as necessary, configure them with those extents, and 
2573         # build lookup table mapping each position for control to its corresponding 
2575 ####        dbg('calculating field extents') 
2577         self
._calcFieldExtents
() 
2580         # Go process defaultValues and fillchars to construct the overall 
2581         # template, and adjust the current value as necessary: 
2582         reset_fillchar 
= reset_args
.has_key('fillChar') and reset_args
['fillChar'] 
2583         reset_default 
= reset_args
.has_key('defaultValue') and reset_args
['defaultValue'] 
2585 ####        dbg('calculating template') 
2586         self
._calcTemplate
(reset_fillchar
, reset_default
) 
2588         # Propagate control-level formatting and character constraints to each 
2589         # field if they don't already have them; if only one field, propagate 
2590         # control-level validation constraints to field as well: 
2591 ####        dbg('propagating constraints') 
2592         self
._propagateConstraints
(**reset_args
) 
2595         if self
._isFloat 
and self
._fields
[0]._groupChar 
== self
._decimalChar
: 
2596             raise AttributeError('groupChar (%s) and decimalChar (%s) must be distinct.' % 
2597                                  (self
._fields
[0]._groupChar
, self
._decimalChar
) ) 
2599 ####        dbg('fields:', indent=1) 
2600 ##        for i in [-1] + self._field_indices: 
2601 ####            dbg('field %d:' % i, self._fields[i].__dict__) 
2604         # Set up special parameters for numeric control, if appropriate: 
2606             self
._signpos 
= 0   # assume it starts here, but it will move around on floats 
2607             signkeys 
= ['-', '+', ' '] 
2609                 signkeys 
+= ['(', ')'] 
2610             for key 
in signkeys
: 
2612                 if not self
._keyhandlers
.has_key(keycode
): 
2613                     self
._SetKeyHandler
(key
, self
._OnChangeSign
) 
2617         if self
._isFloat 
or self
._isInt
: 
2618             if self
.controlInitialized
: 
2619                 value 
= self
._GetValue
() 
2620 ####                dbg('value: "%s"' % value, 'len(value):', len(value), 
2621 ##                    'len(self._ctrl_constraints._mask):',len(self._ctrl_constraints._mask)) 
2622                 if len(value
) < len(self
._ctrl
_constraints
._mask
): 
2624                     if self
._useParens 
and len(newvalue
) < len(self
._ctrl
_constraints
._mask
) and newvalue
.find('(') == -1: 
2626                     if self
._signOk 
and len(newvalue
) < len(self
._ctrl
_constraints
._mask
) and newvalue
.find(')') == -1: 
2627                         newvalue 
= ' ' + newvalue
 
2628                     if len(newvalue
) < len(self
._ctrl
_constraints
._mask
): 
2629                         if self
._ctrl
_constraints
._alignRight
: 
2630                             newvalue 
= newvalue
.rjust(len(self
._ctrl
_constraints
._mask
)) 
2632                             newvalue 
= newvalue
.ljust(len(self
._ctrl
_constraints
._mask
)) 
2633 ##                    dbg('old value: "%s"' % value) 
2634 ##                    dbg('new value: "%s"' % newvalue) 
2636                         self
._SetValue
(newvalue
) 
2637                     except Exception, e
: 
2638 ##                        dbg('exception raised:', e, 'resetting to initial value') 
2639                         self
._SetInitialValue
() 
2641                 elif len(value
) > len(self
._ctrl
_constraints
._mask
): 
2643                     if not self
._useParens 
and newvalue
[-1] == ' ': 
2644                         newvalue 
= newvalue
[:-1] 
2645                     if not self
._signOk 
and len(newvalue
) > len(self
._ctrl
_constraints
._mask
): 
2646                         newvalue 
= newvalue
[1:] 
2647                     if not self
._signOk
: 
2648                         newvalue
, signpos
, right_signpos 
= self
._getSignedValue
(newvalue
) 
2650 ##                    dbg('old value: "%s"' % value) 
2651 ##                    dbg('new value: "%s"' % newvalue) 
2653                         self
._SetValue
(newvalue
) 
2654                     except Exception, e
: 
2655 ##                        dbg('exception raised:', e, 'resetting to initial value') 
2656                         self
._SetInitialValue
() 
2657                 elif not self
._signOk 
and ('(' in value 
or '-' in value
): 
2658                     newvalue
, signpos
, right_signpos 
= self
._getSignedValue
(value
) 
2659 ##                    dbg('old value: "%s"' % value) 
2660 ##                    dbg('new value: "%s"' % newvalue) 
2662                         self
._SetValue
(newvalue
) 
2664 ##                        dbg('exception raised:', e, 'resetting to initial value') 
2665                         self
._SetInitialValue
() 
2667             # Replace up/down arrow default handling: 
2668             # make down act like tab, up act like shift-tab: 
2670 ####            dbg('Registering numeric navigation and control handlers (if not already set)') 
2671             if not self
._keyhandlers
.has_key(wx
.WXK_DOWN
): 
2672                 self
._SetKeycodeHandler
(wx
.WXK_DOWN
, self
._OnChangeField
) 
2673             if not self
._keyhandlers
.has_key(wx
.WXK_UP
): 
2674                 self
._SetKeycodeHandler
(wx
.WXK_UP
, self
._OnUpNumeric
)  # (adds "shift" to up arrow, and calls _OnChangeField) 
2676             # On ., truncate contents right of cursor to decimal point (if any) 
2677             # leaves cusor after decimal point if floating point, otherwise at 0. 
2678             if not self
._keyhandlers
.has_key(ord(self
._decimalChar
)): 
2679                 self
._SetKeyHandler
(self
._decimalChar
, self
._OnDecimalPoint
) 
2680             if not self
._keyhandlers
.has_key(ord(self
._shiftDecimalChar
)): 
2681                 self
._SetKeyHandler
(self
._shiftDecimalChar
, self
._OnChangeField
)   # (Shift-'.' == '>' on US keyboards) 
2683             # Allow selective insert of groupchar in numbers: 
2684             if not self
._keyhandlers
.has_key(ord(self
._fields
[0]._groupChar
)): 
2685                 self
._SetKeyHandler
(self
._fields
[0]._groupChar
, self
._OnGroupChar
) 
2687 ##        dbg(indent=0, suspend=0) 
2690     def _SetInitialValue(self
, value
=""): 
2692         fills the control with the generated or supplied default value. 
2693         It will also set/reset the font if necessary and apply 
2694         formatting to the control at this time. 
2696 ##        dbg('MaskedEditMixin::_SetInitialValue("%s")' % value, indent=1) 
2698             self
._prevValue 
= self
._curValue 
= self
._template
 
2699             # don't apply external validation rules in this case, as template may 
2700             # not coincide with "legal" value... 
2702                 self
._SetValue
(self
._curValue
)  # note the use of "raw" ._SetValue()... 
2703             except Exception, e
: 
2704 ##                dbg('exception thrown:', e, indent=0) 
2707             # Otherwise apply validation as appropriate to passed value: 
2708 ####            dbg('value = "%s", length:' % value, len(value)) 
2709             self
._prevValue 
= self
._curValue 
= value
 
2711                 self
.SetValue(value
)            # use public (validating) .SetValue() 
2712             except Exception, e
: 
2713 ##                dbg('exception thrown:', e, indent=0) 
2717         # Set value/type-specific formatting 
2718         self
._applyFormatting
() 
2722     def _calcSize(self
, size
=None): 
2723         """ Calculate automatic size if allowed; must be called after the base control is instantiated""" 
2724 ####        dbg('MaskedEditMixin::_calcSize', indent=1) 
2725         cont 
= (size 
is None or size 
== wx
.DefaultSize
) 
2727         if cont 
and self
._autofit
: 
2728             sizing_text 
= 'M' * self
._masklength
 
2729             if wx
.Platform 
!= "__WXMSW__":   # give it a little extra space 
2731             if wx
.Platform 
== "__WXMAC__":   # give it even a little more... 
2733 ####            dbg('len(sizing_text):', len(sizing_text), 'sizing_text: "%s"' % sizing_text) 
2734             w
, h 
= self
.GetTextExtent(sizing_text
) 
2735             size 
= (w
+4, self
.GetSize().height
) 
2736 ####            dbg('size:', size, indent=0) 
2741         """ Set the control's font typeface -- pass the font name as str.""" 
2742 ####        dbg('MaskedEditMixin::_setFont', indent=1) 
2743         if not self
._useFixedWidthFont
: 
2744             self
._font 
= wx
.SystemSettings_GetFont(wx
.SYS_DEFAULT_GUI_FONT
) 
2746             font 
= self
.GetFont()   # get size, weight, etc from current font 
2748             # Set to teletype font (guaranteed to be mappable to all wxWindows 
2750             self
._font 
= wx
.Font( font
.GetPointSize(), wx
.TELETYPE
, font
.GetStyle(), 
2751                                  font
.GetWeight(), font
.GetUnderlined()) 
2752 ####            dbg('font string: "%s"' % font.GetNativeFontInfo().ToString()) 
2754         self
.SetFont(self
._font
) 
2758     def _OnTextChange(self
, event
): 
2760         Handler for EVT_TEXT event. 
2761         self._Change() is provided for subclasses, and may return False to 
2762         skip this method logic.  This function returns True if the event 
2763         detected was a legitimate event, or False if it was a "bogus" 
2764         EVT_TEXT event.  (NOTE: There is currently an issue with calling 
2765         .SetValue from within the EVT_CHAR handler that causes duplicate 
2766         EVT_TEXT events for the same change.) 
2768         newvalue 
= self
._GetValue
() 
2769 ##        dbg('MaskedEditMixin::_OnTextChange: value: "%s"' % newvalue, indent=1) 
2771         if self
._ignoreChange
:      # ie. if an "intermediate text change event" 
2775         ##! WS: For some inexplicable reason, every wx.TextCtrl.SetValue 
2776         ## call is generating two (2) EVT_TEXT events.  On certain platforms, 
2777         ## (eg. linux/GTK) the 1st is an empty string value. 
2778         ## This is the only mechanism I can find to mask this problem: 
2779         if newvalue 
== self
._curValue 
or len(newvalue
) == 0: 
2780 ##            dbg('ignoring bogus text change event', indent=0) 
2783 ##            dbg('curvalue: "%s", newvalue: "%s", len(newvalue): %d' % (self._curValue, newvalue, len(newvalue))) 
2785                 if self
._signOk 
and self
._isNeg 
and newvalue
.find('-') == -1 and newvalue
.find('(') == -1: 
2786 ##                    dbg('clearing self._isNeg') 
2788                     text
, self
._signpos
, self
._right
_signpos 
= self
._getSignedValue
() 
2789                 self
._CheckValid
()  # Recolor control as appropriate 
2790 ##            dbg('calling event.Skip()') 
2793         self
._prevValue 
= self
._curValue    
# save for undo 
2794         self
._curValue 
= newvalue           
# Save last seen value for next iteration 
2799     def _OnKeyDown(self
, event
): 
2801         This function allows the control to capture Ctrl-events like Ctrl-tab, 
2802         that are not normally seen by the "cooked" EVT_CHAR routine. 
2804         # Get keypress value, adjusted by control options (e.g. convert to upper etc) 
2805         key    
= event
.GetKeyCode() 
2806         if key 
in self
._nav 
and event
.ControlDown(): 
2807             # then this is the only place we will likely see these events; 
2809 ##            dbg('MaskedEditMixin::OnKeyDown: calling _OnChar') 
2812         # else allow regular EVT_CHAR key processing 
2816     def _OnChar(self
, event
): 
2818         This is the engine of MaskedEdit controls.  It examines each keystroke, 
2819         decides if it's allowed, where it should go or what action to take. 
2821 ##        dbg('MaskedEditMixin::_OnChar', indent=1) 
2823         # Get keypress value, adjusted by control options (e.g. convert to upper etc) 
2824         key 
= event
.GetKeyCode() 
2825         orig_pos 
= self
._GetInsertionPoint
() 
2826         orig_value 
= self
._GetValue
() 
2827 ##        dbg('keycode = ', key) 
2828 ##        dbg('current pos = ', orig_pos) 
2829 ##        dbg('current selection = ', self._GetSelection()) 
2831         if not self
._Keypress
(key
): 
2835         # If no format string for this control, or the control is marked as "read-only", 
2836         # skip the rest of the special processing, and just "do the standard thing:" 
2837         if not self
._mask 
or not self
._IsEditable
(): 
2842         # Process navigation and control keys first, with 
2843         # position/selection unadulterated: 
2844         if key 
in self
._nav 
+ self
._control
: 
2845             if self
._keyhandlers
.has_key(key
): 
2846                 keep_processing 
= self
._keyhandlers
[key
](event
) 
2847                 if self
._GetValue
() != orig_value
: 
2848                     self
.modified 
= True 
2849                 if not keep_processing
: 
2852                 self
._applyFormatting
() 
2856         # Else... adjust the position as necessary for next input key, 
2857         # and determine resulting selection: 
2858         pos 
= self
._adjustPos
( orig_pos
, key 
)    ## get insertion position, adjusted as needed 
2859         sel_start
, sel_to 
= self
._GetSelection
()                ## check for a range of selected text 
2860 ##        dbg("pos, sel_start, sel_to:", pos, sel_start, sel_to) 
2862         keep_processing 
= True 
2863         # Capture user past end of format field 
2864         if pos 
> len(self
.maskdict
): 
2865 ##            dbg("field length exceeded:",pos) 
2866             keep_processing 
= False 
2869             if self
._isMaskChar
(pos
):  ## Get string of allowed characters for validation 
2870                 okchars 
= self
._getAllowedChars
(pos
) 
2872 ##                dbg('Not a valid position: pos = ', pos,"chars=",maskchars) 
2875         key 
= self
._adjustKey
(pos
, key
)     # apply formatting constraints to key: 
2877         if self
._keyhandlers
.has_key(key
): 
2878             # there's an override for default behavior; use override function instead 
2879 ##            dbg('using supplied key handler:', self._keyhandlers[key]) 
2880             keep_processing 
= self
._keyhandlers
[key
](event
) 
2881             if self
._GetValue
() != orig_value
: 
2882                 self
.modified 
= True 
2883             if not keep_processing
: 
2886             # else skip default processing, but do final formatting 
2887         if key 
< wx
.WXK_SPACE 
or key 
> 255: 
2888 ##            dbg('key < WXK_SPACE or key > 255') 
2889             event
.Skip()                # non alphanumeric 
2890             keep_processing 
= False 
2892             field 
= self
._FindField
(pos
) 
2894 ##            dbg("key ='%s'" % chr(key)) 
2896 ##                dbg('okSpaces?', field._okSpaces) 
2899             char 
= chr(key
) # (must work if we got this far) 
2901             if 'unicode' in wx
.PlatformInfo
: 
2902                 char 
= char
.decode(self
._defaultEncoding
) 
2904                 if type(field
._excludeChars
) != types
.UnicodeType
: 
2905                     excludes 
+= field
._excludeChars
.decode(self
._defaultEncoding
) 
2906                 if type(self
._ctrl
_constraints
) != types
.UnicodeType
: 
2907                     excludes 
+= self
._ctrl
_constraints
._excludeChars
.decode(self
._defaultEncoding
) 
2909                 excludes 
= field
._excludeChars 
+ self
._ctrl
_constraints
._excludeChars
 
2911             if char 
in excludes
: 
2912                 keep_processing 
= False 
2914             if keep_processing 
and self
._isCharAllowed
( char
, pos
, checkRegex 
= True ): 
2915 ##                dbg("key allowed by mask") 
2916                 # insert key into candidate new value, but don't change control yet: 
2917                 oldstr 
= self
._GetValue
() 
2918                 newstr
, newpos
, new_select_to
, match_field
, match_index 
= self
._insertKey
( 
2919                                 char
, pos
, sel_start
, sel_to
, self
._GetValue
(), allowAutoSelect 
= True) 
2920 ##                dbg("str with '%s' inserted:" % char, '"%s"' % newstr) 
2921                 if self
._ctrl
_constraints
._validRequired 
and not self
.IsValid(newstr
): 
2922 ##                    dbg('not valid; checking to see if adjusted string is:') 
2923                     keep_processing 
= False 
2924                     if self
._isFloat 
and newstr 
!= self
._template
: 
2925                         newstr 
= self
._adjustFloat
(newstr
) 
2926 ##                        dbg('adjusted str:', newstr) 
2927                         if self
.IsValid(newstr
): 
2929                             keep_processing 
= True 
2930                             wx
.CallAfter(self
._SetInsertionPoint
, self
._decimalpos
) 
2931                     if not keep_processing
: 
2932 ##                        dbg("key disallowed by validation") 
2933                         if not wx
.Validator_IsSilent() and orig_pos 
== pos
: 
2939                     # special case: adjust date value as necessary: 
2940                     if self
._isDate 
and newstr 
!= self
._template
: 
2941                         newstr 
= self
._adjustDate
(newstr
) 
2942 ##                    dbg('adjusted newstr:', newstr) 
2944                     if newstr 
!= orig_value
: 
2945                         self
.modified 
= True 
2947                     wx
.CallAfter(self
._SetValue
, newstr
) 
2949                     # Adjust insertion point on date if just entered 2 digit year, and there are now 4 digits: 
2950                     if not self
.IsDefault() and self
._isDate 
and self
._4digityear
: 
2951                         year2dig 
= self
._dateExtent 
- 2 
2952                         if pos 
== year2dig 
and unadjusted
[year2dig
] != newstr
[year2dig
]: 
2955 ##                    dbg('queuing insertion point: (%d)' % newpos) 
2956                     wx
.CallAfter(self
._SetInsertionPoint
, newpos
) 
2958                     if match_field 
is not None: 
2959 ##                        dbg('matched field') 
2960                         self
._OnAutoSelect
(match_field
, match_index
) 
2962                     if new_select_to 
!= newpos
: 
2963 ##                        dbg('queuing selection: (%d, %d)' % (newpos, new_select_to)) 
2964                         wx
.CallAfter(self
._SetSelection
, newpos
, new_select_to
) 
2966                         newfield 
= self
._FindField
(newpos
) 
2967                         if newfield 
!= field 
and newfield
._selectOnFieldEntry
: 
2968 ##                            dbg('queuing insertion point: (%d)' % newfield._extent[0]) 
2969                             wx
.CallAfter(self
._SetInsertionPoint
, newfield
._extent
[0]) 
2970 ##                            dbg('queuing selection: (%d, %d)' % (newfield._extent[0], newfield._extent[1])) 
2971                             wx
.CallAfter(self
._SetSelection
, newfield
._extent
[0], newfield
._extent
[1]) 
2973                             wx
.CallAfter(self
._SetSelection
, newpos
, new_select_to
) 
2974                     keep_processing 
= False 
2976             elif keep_processing
: 
2977 ##                dbg('char not allowed') 
2978                 keep_processing 
= False 
2979                 if (not wx
.Validator_IsSilent()) and orig_pos 
== pos
: 
2982         self
._applyFormatting
() 
2984         # Move to next insertion point 
2985         if keep_processing 
and key 
not in self
._nav
: 
2986             pos 
= self
._GetInsertionPoint
() 
2987             next_entry 
= self
._findNextEntry
( pos 
) 
2988             if pos 
!= next_entry
: 
2989 ##                dbg("moving from %(pos)d to next valid entry: %(next_entry)d" % locals()) 
2990                 wx
.CallAfter(self
._SetInsertionPoint
, next_entry 
) 
2992             if self
._isTemplateChar
(pos
): 
2993                 self
._AdjustField
(pos
) 
2997     def _FindFieldExtent(self
, pos
=None, getslice
=False, value
=None): 
2998         """ returns editable extent of field corresponding to 
2999         position pos, and, optionally, the contents of that field 
3000         in the control or the value specified. 
3001         Template chars are bound to the preceding field. 
3002         For masks beginning with template chars, these chars are ignored 
3003         when calculating the current field. 
3005         Eg: with template (###) ###-####, 
3006         >>> self._FindFieldExtent(pos=0) 
3008         >>> self._FindFieldExtent(pos=1) 
3010         >>> self._FindFieldExtent(pos=5) 
3012         >>> self._FindFieldExtent(pos=6) 
3014         >>> self._FindFieldExtent(pos=10) 
3018 ##        dbg('MaskedEditMixin::_FindFieldExtent(pos=%s, getslice=%s)' % (str(pos), str(getslice)) ,indent=1) 
3020         field 
= self
._FindField
(pos
) 
3023                 return None, None, "" 
3026         edit_start
, edit_end 
= field
._extent
 
3028             if value 
is None: value 
= self
._GetValue
() 
3029             slice = value
[edit_start
:edit_end
] 
3030 ##            dbg('edit_start:', edit_start, 'edit_end:', edit_end, 'slice: "%s"' % slice) 
3032             return edit_start
, edit_end
, slice 
3034 ##            dbg('edit_start:', edit_start, 'edit_end:', edit_end) 
3036             return edit_start
, edit_end
 
3039     def _FindField(self
, pos
=None): 
3041         Returns the field instance in which pos resides. 
3042         Template chars are bound to the preceding field. 
3043         For masks beginning with template chars, these chars are ignored 
3044         when calculating the current field. 
3047 ####        dbg('MaskedEditMixin::_FindField(pos=%s)' % str(pos) ,indent=1) 
3048         if pos 
is None: pos 
= self
._GetInsertionPoint
() 
3049         elif pos 
< 0 or pos 
> self
._masklength
: 
3050             raise IndexError('position %s out of range of control' % str(pos
)) 
3052         if len(self
._fields
) == 0: 
3058         return self
._fields
[self
._lookupField
[pos
]] 
3061     def ClearValue(self
): 
3062         """ Blanks the current control value by replacing it with the default value.""" 
3063 ##        dbg("MaskedEditMixin::ClearValue - value reset to default value (template)") 
3064         self
._SetValue
( self
._template 
) 
3065         self
._SetInsertionPoint
(0) 
3069     def _baseCtrlEventHandler(self
, event
): 
3071         This function is used whenever a key should be handled by the base control. 
3077     def _OnUpNumeric(self
, event
): 
3079         Makes up-arrow act like shift-tab should; ie. take you to start of 
3082 ##        dbg('MaskedEditMixin::_OnUpNumeric', indent=1) 
3083         event
.m_shiftDown 
= 1 
3084 ##        dbg('event.ShiftDown()?', event.ShiftDown()) 
3085         self
._OnChangeField
(event
) 
3089     def _OnArrow(self
, event
): 
3091         Used in response to left/right navigation keys; makes these actions skip 
3092         over mask template chars. 
3094 ##        dbg("MaskedEditMixin::_OnArrow", indent=1) 
3095         pos 
= self
._GetInsertionPoint
() 
3096         keycode 
= event
.GetKeyCode() 
3097         sel_start
, sel_to 
= self
._GetSelection
() 
3098         entry_end 
= self
._goEnd
(getPosOnly
=True) 
3099         if keycode 
in (wx
.WXK_RIGHT
, wx
.WXK_DOWN
): 
3100             if( ( not self
._isTemplateChar
(pos
) and pos
+1 > entry_end
) 
3101                 or ( self
._isTemplateChar
(pos
) and pos 
>= entry_end
) ): 
3102 ##                dbg("can't advance", indent=0) 
3104             elif self
._isTemplateChar
(pos
): 
3105                 self
._AdjustField
(pos
) 
3106         elif keycode 
in (wx
.WXK_LEFT
,wx
.WXK_UP
) and sel_start 
== sel_to 
and pos 
> 0 and self
._isTemplateChar
(pos
-1): 
3107 ##            dbg('adjusting field') 
3108             self
._AdjustField
(pos
) 
3110         # treat as shifted up/down arrows as tab/reverse tab: 
3111         if event
.ShiftDown() and keycode 
in (wx
.WXK_UP
, wx
.WXK_DOWN
): 
3112             # remove "shifting" and treat as (forward) tab: 
3113             event
.m_shiftDown 
= False 
3114             keep_processing 
= self
._OnChangeField
(event
) 
3116         elif self
._FindField
(pos
)._selectOnFieldEntry
: 
3117             if( keycode 
in (wx
.WXK_UP
, wx
.WXK_LEFT
) 
3119                 and self
._isTemplateChar
(sel_start
-1) 
3120                 and sel_start 
!= self
._masklength
 
3121                 and not self
._signOk 
and not self
._useParens
): 
3123                 # call _OnChangeField to handle "ctrl-shifted event" 
3124                 # (which moves to previous field and selects it.) 
3125                 event
.m_shiftDown 
= True 
3126                 event
.m_ControlDown 
= True 
3127                 keep_processing 
= self
._OnChangeField
(event
) 
3128             elif( keycode 
in (wx
.WXK_DOWN
, wx
.WXK_RIGHT
) 
3129                   and sel_to 
!= self
._masklength
 
3130                   and self
._isTemplateChar
(sel_to
)): 
3132                 # when changing field to the right, ensure don't accidentally go left instead 
3133                 event
.m_shiftDown 
= False 
3134                 keep_processing 
= self
._OnChangeField
(event
) 
3136                 # treat arrows as normal, allowing selection 
3138 ##                dbg('using base ctrl event processing') 
3141             if( (sel_to 
== self
._fields
[0]._extent
[0] and keycode 
== wx
.WXK_LEFT
) 
3142                 or (sel_to 
== self
._masklength 
and keycode 
== wx
.WXK_RIGHT
) ): 
3143                 if not wx
.Validator_IsSilent(): 
3146                 # treat arrows as normal, allowing selection 
3148 ##                dbg('using base event processing') 
3151         keep_processing 
= False 
3153         return keep_processing
 
3156     def _OnCtrl_S(self
, event
): 
3157         """ Default Ctrl-S handler; prints value information if demo enabled. """ 
3158 ##        dbg("MaskedEditMixin::_OnCtrl_S") 
3160             print 'MaskedEditMixin.GetValue()       = "%s"\nMaskedEditMixin.GetPlainValue() = "%s"' % (self
.GetValue(), self
.GetPlainValue()) 
3161             print "Valid? => " + str(self
.IsValid()) 
3162             print "Current field, start, end, value =", str( self
._FindFieldExtent
(getslice
=True)) 
3166     def _OnCtrl_X(self
, event
=None): 
3167         """ Handles ctrl-x keypress in control and Cut operation on context menu. 
3168             Should return False to skip other processing. """ 
3169 ##        dbg("MaskedEditMixin::_OnCtrl_X", indent=1) 
3174     def _OnCtrl_C(self
, event
=None): 
3175         """ Handles ctrl-C keypress in control and Copy operation on context menu. 
3176             Uses base control handling. Should return False to skip other processing.""" 
3180     def _OnCtrl_V(self
, event
=None): 
3181         """ Handles ctrl-V keypress in control and Paste operation on context menu. 
3182             Should return False to skip other processing. """ 
3183 ##        dbg("MaskedEditMixin::_OnCtrl_V", indent=1) 
3188     def _OnCtrl_Z(self
, event
=None): 
3189         """ Handles ctrl-Z keypress in control and Undo operation on context menu. 
3190             Should return False to skip other processing. """ 
3191 ##        dbg("MaskedEditMixin::_OnCtrl_Z", indent=1) 
3196     def _OnCtrl_A(self
,event
=None): 
3197         """ Handles ctrl-a keypress in control. Should return False to skip other processing. """ 
3198         end 
= self
._goEnd
(getPosOnly
=True) 
3199         if not event 
or (isinstance(event
, wx
.KeyEvent
) and event
.ShiftDown()): 
3200             wx
.CallAfter(self
._SetInsertionPoint
, 0) 
3201             wx
.CallAfter(self
._SetSelection
, 0, self
._masklength
) 
3203             wx
.CallAfter(self
._SetInsertionPoint
, 0) 
3204             wx
.CallAfter(self
._SetSelection
, 0, end
) 
3208     def _OnErase(self
, event
=None, just_return_value
=False): 
3209         """ Handles backspace and delete keypress in control. Should return False to skip other processing.""" 
3210 ##        dbg("MaskedEditMixin::_OnErase", indent=1) 
3211         sel_start
, sel_to 
= self
._GetSelection
()                   ## check for a range of selected text 
3213         if event 
is None:   # called as action routine from Cut() operation. 
3216             key 
= event
.GetKeyCode() 
3218         field 
= self
._FindField
(sel_to
) 
3219         start
, end 
= field
._extent
 
3220         value 
= self
._GetValue
() 
3221         oldstart 
= sel_start
 
3223         # If trying to erase beyond "legal" bounds, disallow operation: 
3224         if( (sel_to 
== 0 and key 
== wx
.WXK_BACK
) 
3225             or (self
._signOk 
and sel_to 
== 1 and value
[0] == ' ' and key 
== wx
.WXK_BACK
) 
3226             or (sel_to 
== self
._masklength 
and sel_start 
== sel_to 
and key 
== wx
.WXK_DELETE 
and not field
._insertRight
) 
3227             or (self
._signOk 
and self
._useParens
 
3228                 and sel_start 
== sel_to
 
3229                 and sel_to 
== self
._masklength 
- 1 
3230                 and value
[sel_to
] == ' ' and key 
== wx
.WXK_DELETE 
and not field
._insertRight
) ): 
3231             if not wx
.Validator_IsSilent(): 
3237         if( field
._insertRight                                  
# an insert-right field 
3238             and value
[start
:end
] != self
._template
[start
:end
]   # and field not empty 
3239             and sel_start 
>= start                              
# and selection starts in field 
3240             and ((sel_to 
== sel_start                           
# and no selection 
3241                   and sel_to 
== end                             
# and cursor at right edge 
3242                   and key 
in (wx
.WXK_BACK
, wx
.WXK_DELETE
))            # and either delete or backspace key 
3244                  (key 
== wx
.WXK_BACK                               
# backspacing 
3245                     and (sel_to 
== end                          
# and selection ends at right edge 
3246                          or sel_to 
< end 
and field
._allowInsert
)) ) ):  # or allow right insert at any point in field 
3248 ##            dbg('delete left') 
3249             # if backspace but left of cursor is empty, adjust cursor right before deleting 
3250             while( key 
== wx
.WXK_BACK
 
3251                    and sel_start 
== sel_to
 
3253                    and value
[start
:sel_start
] == self
._template
[start
:sel_start
]): 
3257 ##            dbg('sel_start, start:', sel_start, start) 
3259             if sel_start 
== sel_to
: 
3263             newfield 
= value
[start
:keep
] + value
[sel_to
:end
] 
3265             # handle sign char moving from outside field into the field: 
3266             move_sign_into_field 
= False 
3267             if not field
._padZero 
and self
._signOk 
and self
._isNeg 
and value
[0] in ('-', '('): 
3269                 newfield 
= signchar 
+ newfield
 
3270                 move_sign_into_field 
= True 
3271 ##            dbg('cut newfield: "%s"' % newfield) 
3273             # handle what should fill in from the left: 
3275             for i 
in range(start
, end 
- len(newfield
)): 
3278                 elif( self
._signOk 
and self
._isNeg 
and i 
== 1 
3279                       and ((self
._useParens 
and newfield
.find('(') == -1) 
3280                            or (not self
._useParens 
and newfield
.find('-') == -1)) ): 
3283                     left 
+= self
._template
[i
]   # this can produce strange results in combination with default values... 
3284             newfield 
= left 
+ newfield
 
3285 ##            dbg('filled newfield: "%s"' % newfield) 
3287             newstr 
= value
[:start
] + newfield 
+ value
[end
:] 
3289             # (handle sign located in "mask position" in front of field prior to delete) 
3290             if move_sign_into_field
: 
3291                 newstr 
= ' ' + newstr
[1:] 
3294             # handle erasure of (left) sign, moving selection accordingly... 
3295             if self
._signOk 
and sel_start 
== 0: 
3296                 newstr 
= value 
= ' ' + value
[1:] 
3299             if field
._allowInsert 
and sel_start 
>= start
: 
3300                 # selection (if any) falls within current insert-capable field: 
3301                 select_len 
= sel_to 
- sel_start
 
3302                 # determine where cursor should end up: 
3303                 if key 
== wx
.WXK_BACK
: 
3305                         newpos 
= sel_start 
-1 
3311                     if sel_to 
== sel_start
: 
3312                         erase_to 
= sel_to 
+ 1 
3316                 if self
._isTemplateChar
(newpos
) and select_len 
== 0: 
3318                         if value
[newpos
] in ('(', '-'): 
3319                             newpos 
+= 1     # don't move cusor 
3320                             newstr 
= ' ' + value
[newpos
:] 
3321                         elif value
[newpos
] == ')': 
3322                             # erase right sign, but don't move cursor; (matching left sign handled later) 
3323                             newstr 
= value
[:newpos
] + ' ' 
3325                             # no deletion; just move cursor 
3328                         # no deletion; just move cursor 
3331                     if erase_to 
> end
: erase_to 
= end
 
3332                     erase_len 
= erase_to 
- newpos
 
3334                     left 
= value
[start
:newpos
] 
3335 ##                    dbg("retained ='%s'" % value[erase_to:end], 'sel_to:', sel_to, "fill: '%s'" % self._template[end - erase_len:end]) 
3336                     right 
= value
[erase_to
:end
] + self
._template
[end
-erase_len
:end
] 
3338                     if field
._alignRight
: 
3339                         rstripped 
= right
.rstrip() 
3340                         if rstripped 
!= right
: 
3341                             pos_adjust 
= len(right
) - len(rstripped
) 
3344                     if not field
._insertRight 
and value
[-1] == ')' and end 
== self
._masklength 
- 1: 
3345                         # need to shift ) into the field: 
3346                         right 
= right
[:-1] + ')' 
3347                         value 
= value
[:-1] + ' ' 
3349                     newfield 
= left
+right
 
3351                         newfield 
= newfield
.rjust(end
-start
) 
3352                         newpos 
+= pos_adjust
 
3353 ##                    dbg("left='%s', right ='%s', newfield='%s'" %(left, right, newfield)) 
3354                     newstr 
= value
[:start
] + newfield 
+ value
[end
:] 
3359                 if sel_start 
== sel_to
: 
3360 ##                    dbg("current sel_start, sel_to:", sel_start, sel_to) 
3361                     if key 
== wx
.WXK_BACK
: 
3362                         sel_start
, sel_to 
= sel_to
-1, sel_to
-1 
3363 ##                        dbg("new sel_start, sel_to:", sel_start, sel_to) 
3365                     if field
._padZero 
and not value
[start
:sel_to
].replace('0', '').replace(' ','').replace(field
._fillChar
, ''): 
3366                         # preceding chars (if any) are zeros, blanks or fillchar; new char should be 0: 
3369                         newchar 
= self
._template
[sel_to
] ## get an original template character to "clear" the current char 
3370 ##                    dbg('value = "%s"' % value, 'value[%d] = "%s"' %(sel_start, value[sel_start])) 
3372                     if self
._isTemplateChar
(sel_to
): 
3373                         if sel_to 
== 0 and self
._signOk 
and value
[sel_to
] == '-':   # erasing "template" sign char 
3374                             newstr 
= ' ' + value
[1:] 
3376                         elif self
._signOk 
and self
._useParens 
and (value
[sel_to
] == ')' or value
[sel_to
] == '('): 
3377                             # allow "change sign" by removing both parens: 
3378                             newstr 
= value
[:self
._signpos
] + ' ' + value
[self
._signpos
+1:-1] + ' ' 
3383                         if field
._insertRight 
and sel_start 
== sel_to
: 
3384                             # force non-insert-right behavior, by selecting char to be replaced: 
3386                         newstr
, ignore 
= self
._insertKey
(newchar
, sel_start
, sel_start
, sel_to
, value
) 
3390                     newstr 
= self
._eraseSelection
(value
, sel_start
, sel_to
) 
3392                 pos 
= sel_start  
# put cursor back at beginning of selection 
3394         if self
._signOk 
and self
._useParens
: 
3395             # account for resultant unbalanced parentheses: 
3396             left_signpos 
= newstr
.find('(') 
3397             right_signpos 
= newstr
.find(')') 
3399             if left_signpos 
== -1 and right_signpos 
!= -1: 
3400                 # erased left-sign marker; get rid of right sign marker: 
3401                 newstr 
= newstr
[:right_signpos
] + ' ' + newstr
[right_signpos
+1:] 
3403             elif left_signpos 
!= -1 and right_signpos 
== -1: 
3404                 # erased right-sign marker; get rid of left-sign marker: 
3405                 newstr 
= newstr
[:left_signpos
] + ' ' + newstr
[left_signpos
+1:] 
3407 ##        dbg("oldstr:'%s'" % value, 'oldpos:', oldstart) 
3408 ##        dbg("newstr:'%s'" % newstr, 'pos:', pos) 
3410         # if erasure results in an invalid field, disallow it: 
3411 ##        dbg('field._validRequired?', field._validRequired) 
3412 ##        dbg('field.IsValid("%s")?' % newstr[start:end], field.IsValid(newstr[start:end])) 
3413         if field
._validRequired 
and not field
.IsValid(newstr
[start
:end
]): 
3414             if not wx
.Validator_IsSilent(): 
3419         # if erasure results in an invalid value, disallow it: 
3420         if self
._ctrl
_constraints
._validRequired 
and not self
.IsValid(newstr
): 
3421             if not wx
.Validator_IsSilent(): 
3426         if just_return_value
: 
3431 ##        dbg('setting value (later) to', newstr) 
3432         wx
.CallAfter(self
._SetValue
, newstr
) 
3433 ##        dbg('setting insertion point (later) to', pos) 
3434         wx
.CallAfter(self
._SetInsertionPoint
, pos
) 
3437             self
.modified 
= True 
3441     def _OnEnd(self
,event
): 
3442         """ Handles End keypress in control. Should return False to skip other processing. """ 
3443 ##        dbg("MaskedEditMixin::_OnEnd", indent=1) 
3444         pos 
= self
._adjustPos
(self
._GetInsertionPoint
(), event
.GetKeyCode()) 
3445         if not event
.ControlDown(): 
3446             end 
= self
._masklength  
# go to end of control 
3447             if self
._signOk 
and self
._useParens
: 
3448                 end 
= end 
- 1       # account for reserved char at end 
3450             end_of_input 
= self
._goEnd
(getPosOnly
=True) 
3451             sel_start
, sel_to 
= self
._GetSelection
() 
3452             if sel_to 
< pos
: sel_to 
= pos
 
3453             field 
= self
._FindField
(sel_to
) 
3454             field_end 
= self
._FindField
(end_of_input
) 
3456             # pick different end point if either: 
3457             # - cursor not in same field 
3458             # - or at or past last input already 
3459             # - or current selection = end of current field: 
3460 ####            dbg('field != field_end?', field != field_end) 
3461 ####            dbg('sel_to >= end_of_input?', sel_to >= end_of_input) 
3462             if field 
!= field_end 
or sel_to 
>= end_of_input
: 
3463                 edit_start
, edit_end 
= field
._extent
 
3464 ####                dbg('edit_end:', edit_end) 
3465 ####                dbg('sel_to:', sel_to) 
3466 ####                dbg('sel_to == edit_end?', sel_to == edit_end) 
3467 ####                dbg('field._index < self._field_indices[-1]?', field._index < self._field_indices[-1]) 
3469                 if sel_to 
== edit_end 
and field
._index 
< self
._field
_indices
[-1]: 
3470                     edit_start
, edit_end 
= self
._FindFieldExtent
(self
._findNextEntry
(edit_end
))  # go to end of next field: 
3472 ##                    dbg('end moved to', end) 
3474                 elif sel_to 
== edit_end 
and field
._index 
== self
._field
_indices
[-1]: 
3475                     # already at edit end of last field; select to end of control: 
3476                     end 
= self
._masklength
 
3477 ##                    dbg('end moved to', end) 
3479                     end 
= edit_end  
# select to end of current field 
3480 ##                    dbg('end moved to ', end) 
3482                 # select to current end of input 
3486 ####        dbg('pos:', pos, 'end:', end) 
3488         if event
.ShiftDown(): 
3489             if not event
.ControlDown(): 
3490 ##                dbg("shift-end; select to end of control") 
3493 ##                dbg("shift-ctrl-end; select to end of non-whitespace") 
3495             wx
.CallAfter(self
._SetInsertionPoint
, pos
) 
3496             wx
.CallAfter(self
._SetSelection
, pos
, end
) 
3498             if not event
.ControlDown(): 
3499 ##                dbg('go to end of control:') 
3501             wx
.CallAfter(self
._SetInsertionPoint
, end
) 
3502             wx
.CallAfter(self
._SetSelection
, end
, end
) 
3508     def _OnReturn(self
, event
): 
3510          Swallows the return, issues a Navigate event instead, since 
3511          masked controls are "single line" by defn. 
3513 ##         dbg('MaskedEditMixin::OnReturn') 
3518     def _OnHome(self
,event
): 
3519         """ Handles Home keypress in control. Should return False to skip other processing.""" 
3520 ##        dbg("MaskedEditMixin::_OnHome", indent=1) 
3521         pos 
= self
._adjustPos
(self
._GetInsertionPoint
(), event
.GetKeyCode()) 
3522         sel_start
, sel_to 
= self
._GetSelection
() 
3524         # There are 5 cases here: 
3526         # 1) shift: select from start of control to end of current 
3528         if event
.ShiftDown() and not event
.ControlDown(): 
3529 ##            dbg("shift-home; select to start of control") 
3533         # 2) no shift, no control: move cursor to beginning of control. 
3534         elif not event
.ControlDown(): 
3535 ##            dbg("home; move to start of control") 
3539         # 3) No shift, control: move cursor back to beginning of field; if 
3540         #    there already, go to beginning of previous field. 
3541         # 4) shift, control, start of selection not at beginning of control: 
3542         #    move sel_start back to start of field; if already there, go to 
3543         #    start of previous field. 
3544         elif( event
.ControlDown() 
3545               and (not event
.ShiftDown() 
3546                    or (event
.ShiftDown() and sel_start 
> 0) ) ): 
3547             if len(self
._field
_indices
) > 1: 
3548                 field 
= self
._FindField
(sel_start
) 
3549                 start
, ignore 
= field
._extent
 
3550                 if sel_start 
== start 
and field
._index 
!= self
._field
_indices
[0]:  # go to start of previous field: 
3551                     start
, ignore 
= self
._FindFieldExtent
(sel_start
-1) 
3552                 elif sel_start 
== start
: 
3553                     start 
= 0   # go to literal beginning if edit start 
3560             if not event
.ShiftDown(): 
3561 ##                dbg("ctrl-home; move to beginning of field") 
3564 ##                dbg("shift-ctrl-home; select to beginning of field") 
3568         # 5) shift, control, start of selection at beginning of control: 
3569         #    unselect by moving sel_to backward to beginning of current field; 
3570         #    if already there, move to start of previous field. 
3572             if len(self
._field
_indices
) > 1: 
3573                 # find end of previous field: 
3574                 field 
= self
._FindField
(sel_to
) 
3575                 if sel_to 
> start 
and field
._index 
!= self
._field
_indices
[0]: 
3576                     ignore
, end 
= self
._FindFieldExtent
(field
._extent
[0]-1) 
3582                 end_of_field 
= False 
3583 ##            dbg("shift-ctrl-home; unselect to beginning of field") 
3585 ##        dbg('queuing new sel_start, sel_to:', (start, end)) 
3586         wx
.CallAfter(self
._SetInsertionPoint
, start
) 
3587         wx
.CallAfter(self
._SetSelection
, start
, end
) 
3592     def _OnChangeField(self
, event
): 
3594         Primarily handles TAB events, but can be used for any key that 
3595         designer wants to change fields within a masked edit control. 
3597 ##        dbg('MaskedEditMixin::_OnChangeField', indent = 1) 
3598         # determine end of current field: 
3599         pos 
= self
._GetInsertionPoint
() 
3600 ##        dbg('current pos:', pos) 
3601         sel_start
, sel_to 
= self
._GetSelection
() 
3603         if self
._masklength 
< 0:   # no fields; process tab normally 
3604             self
._AdjustField
(pos
) 
3605             if event
.GetKeyCode() == wx
.WXK_TAB
: 
3606 ##                dbg('tab to next ctrl') 
3607                 # As of 2.5.2, you don't call event.Skip() to do 
3608                 # this, but instead force explicit navigation, if 
3609                 # wx.TE_PROCESS_TAB is used (like in the masked edits) 
3616         if event
.ShiftDown(): 
3620             # NOTE: doesn't yet work with SHIFT-tab under wx; the control 
3621             # never sees this event! (But I've coded for it should it ever work, 
3622             # and it *does* work for '.' in IpAddrCtrl.) 
3623             field 
= self
._FindField
(pos
) 
3624             index 
= field
._index
 
3625             field_start 
= field
._extent
[0] 
3626             if pos 
< field_start
: 
3627 ##                dbg('cursor before 1st field; cannot change to a previous field') 
3628                 if not wx
.Validator_IsSilent(): 
3632             if event
.ControlDown(): 
3633 ##                dbg('queuing select to beginning of field:', field_start, pos) 
3634                 wx
.CallAfter(self
._SetInsertionPoint
, field_start
) 
3635                 wx
.CallAfter(self
._SetSelection
, field_start
, pos
) 
3640                   # We're already in the 1st field; process shift-tab normally: 
3641                 self
._AdjustField
(pos
) 
3642                 if event
.GetKeyCode() == wx
.WXK_TAB
: 
3643 ##                    dbg('tab to previous ctrl') 
3644                     # As of 2.5.2, you don't call event.Skip() to do 
3645                     # this, but instead force explicit navigation, if 
3646                     # wx.TE_PROCESS_TAB is used (like in the masked edits) 
3647                     self
.Navigate(False) 
3649 ##                    dbg('position at beginning') 
3650                     wx
.CallAfter(self
._SetInsertionPoint
, field_start
) 
3654                 # find beginning of previous field: 
3655                 begin_prev 
= self
._FindField
(field_start
-1)._extent
[0] 
3656                 self
._AdjustField
(pos
) 
3657 ##                dbg('repositioning to', begin_prev) 
3658                 wx
.CallAfter(self
._SetInsertionPoint
, begin_prev
) 
3659                 if self
._FindField
(begin_prev
)._selectOnFieldEntry
: 
3660                     edit_start
, edit_end 
= self
._FindFieldExtent
(begin_prev
) 
3661 ##                    dbg('queuing selection to (%d, %d)' % (edit_start, edit_end)) 
3662                     wx
.CallAfter(self
._SetInsertionPoint
, edit_start
) 
3663                     wx
.CallAfter(self
._SetSelection
, edit_start
, edit_end
) 
3669             field 
= self
._FindField
(sel_to
) 
3670             field_start
, field_end 
= field
._extent
 
3671             if event
.ControlDown(): 
3672 ##                dbg('queuing select to end of field:', pos, field_end) 
3673                 wx
.CallAfter(self
._SetInsertionPoint
, pos
) 
3674                 wx
.CallAfter(self
._SetSelection
, pos
, field_end
) 
3678                 if pos 
< field_start
: 
3679 ##                    dbg('cursor before 1st field; go to start of field') 
3680                     wx
.CallAfter(self
._SetInsertionPoint
, field_start
) 
3681                     if field
._selectOnFieldEntry
: 
3682                         wx
.CallAfter(self
._SetSelection
, field_start
, field_end
) 
3684                         wx
.CallAfter(self
._SetSelection
, field_start
, field_start
) 
3687 ##                dbg('end of current field:', field_end) 
3688 ##                dbg('go to next field') 
3689                 if field_end 
== self
._fields
[self
._field
_indices
[-1]]._extent
[1]: 
3690                     self
._AdjustField
(pos
) 
3691                     if event
.GetKeyCode() == wx
.WXK_TAB
: 
3692 ##                        dbg('tab to next ctrl') 
3693                         # As of 2.5.2, you don't call event.Skip() to do 
3694                         # this, but instead force explicit navigation, if 
3695                         # wx.TE_PROCESS_TAB is used (like in the masked edits) 
3698 ##                        dbg('position at end') 
3699                         wx
.CallAfter(self
._SetInsertionPoint
, field_end
) 
3703                     # we have to find the start of the next field 
3704                     next_pos 
= self
._findNextEntry
(field_end
) 
3705                     if next_pos 
== field_end
: 
3706 ##                        dbg('already in last field') 
3707                         self
._AdjustField
(pos
) 
3708                         if event
.GetKeyCode() == wx
.WXK_TAB
: 
3709 ##                            dbg('tab to next ctrl') 
3710                             # As of 2.5.2, you don't call event.Skip() to do 
3711                             # this, but instead force explicit navigation, if 
3712                             # wx.TE_PROCESS_TAB is used (like in the masked edits) 
3718                         self
._AdjustField
( pos 
) 
3720                         # move cursor to appropriate point in the next field and select as necessary: 
3721                         field 
= self
._FindField
(next_pos
) 
3722                         edit_start
, edit_end 
= field
._extent
 
3723                         if field
._selectOnFieldEntry
: 
3724 ##                            dbg('move to ', next_pos) 
3725                             wx
.CallAfter(self
._SetInsertionPoint
, next_pos
) 
3726                             edit_start
, edit_end 
= self
._FindFieldExtent
(next_pos
) 
3727 ##                            dbg('queuing select', edit_start, edit_end) 
3728                             wx
.CallAfter(self
._SetSelection
, edit_start
, edit_end
) 
3730                             if field
._insertRight
: 
3731                                 next_pos 
= field
._extent
[1] 
3732 ##                            dbg('move to ', next_pos) 
3733                             wx
.CallAfter(self
._SetInsertionPoint
, next_pos
) 
3738     def _OnDecimalPoint(self
, event
): 
3739 ##        dbg('MaskedEditMixin::_OnDecimalPoint', indent=1) 
3741         pos 
= self
._adjustPos
(self
._GetInsertionPoint
(), event
.GetKeyCode()) 
3743         if self
._isFloat
:       ## handle float value, move to decimal place 
3744 ##            dbg('key == Decimal tab; decimal pos:', self._decimalpos) 
3745             value 
= self
._GetValue
() 
3746             if pos 
< self
._decimalpos
: 
3747                 clipped_text 
= value
[0:pos
] + self
._decimalChar 
+ value
[self
._decimalpos
+1:] 
3748 ##                dbg('value: "%s"' % self._GetValue(), "clipped_text:'%s'" % clipped_text) 
3749                 newstr 
= self
._adjustFloat
(clipped_text
) 
3751                 newstr 
= self
._adjustFloat
(value
) 
3752             wx
.CallAfter(self
._SetValue
, newstr
) 
3753             fraction 
= self
._fields
[1] 
3754             start
, end 
= fraction
._extent
 
3755             wx
.CallAfter(self
._SetInsertionPoint
, start
) 
3756             if fraction
._selectOnFieldEntry
: 
3757 ##                dbg('queuing selection after decimal point to:', (start, end)) 
3758                 wx
.CallAfter(self
._SetSelection
, start
, end
) 
3760                 wx
.CallAfter(self
._SetSelection
, start
, start
) 
3761             keep_processing 
= False 
3763         if self
._isInt
:      ## handle integer value, truncate from current position 
3764 ##            dbg('key == Integer decimal event') 
3765             value 
= self
._GetValue
() 
3766             clipped_text 
= value
[0:pos
] 
3767 ##            dbg('value: "%s"' % self._GetValue(), "clipped_text:'%s'" % clipped_text) 
3768             newstr 
= self
._adjustInt
(clipped_text
) 
3769 ##            dbg('newstr: "%s"' % newstr) 
3770             wx
.CallAfter(self
._SetValue
, newstr
) 
3771             newpos 
= len(newstr
.rstrip()) 
3772             if newstr
.find(')') != -1: 
3773                 newpos 
-= 1     # (don't move past right paren) 
3774             wx
.CallAfter(self
._SetInsertionPoint
, newpos
) 
3775             wx
.CallAfter(self
._SetSelection
, newpos
, newpos
) 
3776             keep_processing 
= False 
3780     def _OnChangeSign(self
, event
): 
3781 ##        dbg('MaskedEditMixin::_OnChangeSign', indent=1) 
3782         key 
= event
.GetKeyCode() 
3783         pos 
= self
._adjustPos
(self
._GetInsertionPoint
(), key
) 
3784         value 
= self
._eraseSelection
() 
3785         integer 
= self
._fields
[0] 
3786         start
, end 
= integer
._extent
 
3788 ####        dbg('adjusted pos:', pos) 
3789         if chr(key
) in ('-','+','(', ')') or (chr(key
) == " " and pos 
== self
._signpos
): 
3790             cursign 
= self
._isNeg
 
3791 ##            dbg('cursign:', cursign) 
3792             if chr(key
) in ('-','(', ')'): 
3793                 self
._isNeg 
= (not self
._isNeg
)   ## flip value 
3796 ##            dbg('isNeg?', self._isNeg) 
3798             text
, self
._signpos
, self
._right
_signpos 
= self
._getSignedValue
(candidate
=value
) 
3799 ##            dbg('text:"%s"' % text, 'signpos:', self._signpos, 'right_signpos:', self._right_signpos) 
3803             if self
._isNeg 
and self
._signpos 
is not None and self
._signpos 
!= -1: 
3804                 if self
._useParens 
and self
._right
_signpos 
is not None: 
3805                     text 
= text
[:self
._signpos
] + '(' + text
[self
._signpos
+1:self
._right
_signpos
] + ')' + text
[self
._right
_signpos
+1:] 
3807                     text 
= text
[:self
._signpos
] + '-' + text
[self
._signpos
+1:] 
3809 ####                dbg('self._isNeg?', self._isNeg, 'self.IsValid(%s)' % text, self.IsValid(text)) 
3811                     text 
= text
[:self
._signpos
] + ' ' + text
[self
._signpos
+1:self
._right
_signpos
] + ' ' + text
[self
._right
_signpos
+1:] 
3813                     text 
= text
[:self
._signpos
] + ' ' + text
[self
._signpos
+1:] 
3814 ##                dbg('clearing self._isNeg') 
3817             wx
.CallAfter(self
._SetValue
, text
) 
3818             wx
.CallAfter(self
._applyFormatting
) 
3819 ##            dbg('pos:', pos, 'signpos:', self._signpos) 
3820             if pos 
== self
._signpos 
or integer
.IsEmpty(text
[start
:end
]): 
3821                 wx
.CallAfter(self
._SetInsertionPoint
, self
._signpos
+1) 
3823                 wx
.CallAfter(self
._SetInsertionPoint
, pos
) 
3825             keep_processing 
= False 
3827             keep_processing 
= True 
3829         return keep_processing
 
3832     def _OnGroupChar(self
, event
): 
3834         This handler is only registered if the mask is a numeric mask. 
3835         It allows the insertion of ',' or '.' if appropriate. 
3837 ##        dbg('MaskedEditMixin::_OnGroupChar', indent=1) 
3838         keep_processing 
= True 
3839         pos 
= self
._adjustPos
(self
._GetInsertionPoint
(), event
.GetKeyCode()) 
3840         sel_start
, sel_to 
= self
._GetSelection
() 
3841         groupchar 
= self
._fields
[0]._groupChar
 
3842         if not self
._isCharAllowed
(groupchar
, pos
, checkRegex
=True): 
3843             keep_processing 
= False 
3844             if not wx
.Validator_IsSilent(): 
3848             newstr
, newpos 
= self
._insertKey
(groupchar
, pos
, sel_start
, sel_to
, self
._GetValue
() ) 
3849 ##            dbg("str with '%s' inserted:" % groupchar, '"%s"' % newstr) 
3850             if self
._ctrl
_constraints
._validRequired 
and not self
.IsValid(newstr
): 
3851                 keep_processing 
= False 
3852                 if not wx
.Validator_IsSilent(): 
3856             wx
.CallAfter(self
._SetValue
, newstr
) 
3857             wx
.CallAfter(self
._SetInsertionPoint
, newpos
) 
3858         keep_processing 
= False 
3860         return keep_processing
 
3863     def _findNextEntry(self
,pos
, adjustInsert
=True): 
3864         """ Find the insertion point for the next valid entry character position.""" 
3865         if self
._isTemplateChar
(pos
):   # if changing fields, pay attn to flag 
3866             adjustInsert 
= adjustInsert
 
3867         else:                           # else within a field; flag not relevant 
3868             adjustInsert 
= False 
3870         while self
._isTemplateChar
(pos
) and pos 
< self
._masklength
: 
3873         # if changing fields, and we've been told to adjust insert point, 
3874         # look at new field; if empty and right-insert field, 
3875         # adjust to right edge: 
3876         if adjustInsert 
and pos 
< self
._masklength
: 
3877             field 
= self
._FindField
(pos
) 
3878             start
, end 
= field
._extent
 
3879             slice = self
._GetValue
()[start
:end
] 
3880             if field
._insertRight 
and field
.IsEmpty(slice): 
3885     def _findNextTemplateChar(self
, pos
): 
3886         """ Find the position of the next non-editable character in the mask.""" 
3887         while not self
._isTemplateChar
(pos
) and pos 
< self
._masklength
: 
3892     def _OnAutoCompleteField(self
, event
): 
3893 ##        dbg('MaskedEditMixin::_OnAutoCompleteField', indent =1) 
3894         pos 
= self
._GetInsertionPoint
() 
3895         field 
= self
._FindField
(pos
) 
3896         edit_start
, edit_end
, slice = self
._FindFieldExtent
(pos
, getslice
=True) 
3899         keycode 
= event
.GetKeyCode() 
3901         if field
._fillChar 
!= ' ': 
3902             text 
= slice.replace(field
._fillChar
, '') 
3906         keep_processing 
= True  # (assume True to start) 
3907 ##        dbg('field._hasList?', field._hasList) 
3909 ##            dbg('choices:', field._choices) 
3910 ##            dbg('compareChoices:', field._compareChoices) 
3911             choices
, choice_required 
= field
._compareChoices
, field
._choiceRequired
 
3912             if keycode 
in (wx
.WXK_PRIOR
, wx
.WXK_UP
): 
3916             match_index
, partial_match 
= self
._autoComplete
(direction
, choices
, text
, compareNoCase
=field
._compareNoCase
, current_index 
= field
._autoCompleteIndex
) 
3917             if( match_index 
is None 
3918                 and (keycode 
in self
._autoCompleteKeycodes 
+ [wx
.WXK_PRIOR
, wx
.WXK_NEXT
] 
3919                      or (keycode 
in [wx
.WXK_UP
, wx
.WXK_DOWN
] and event
.ShiftDown() ) ) ): 
3920                 # Select the 1st thing from the list: 
3923             if( match_index 
is not None 
3924                 and ( keycode 
in self
._autoCompleteKeycodes 
+ [wx
.WXK_PRIOR
, wx
.WXK_NEXT
] 
3925                       or (keycode 
in [wx
.WXK_UP
, wx
.WXK_DOWN
] and event
.ShiftDown()) 
3926                       or (keycode 
== wx
.WXK_DOWN 
and partial_match
) ) ): 
3928                 # We're allowed to auto-complete: 
3929 ##                dbg('match found') 
3930                 value 
= self
._GetValue
() 
3931                 newvalue 
= value
[:edit_start
] + field
._choices
[match_index
] + value
[edit_end
:] 
3932 ##                dbg('setting value to "%s"' % newvalue) 
3933                 self
._SetValue
(newvalue
) 
3934                 self
._SetInsertionPoint
(min(edit_end
, len(newvalue
.rstrip()))) 
3935                 self
._OnAutoSelect
(field
, match_index
) 
3936                 self
._CheckValid
()  # recolor as appopriate 
3939         if keycode 
in (wx
.WXK_UP
, wx
.WXK_DOWN
, wx
.WXK_LEFT
, wx
.WXK_RIGHT
): 
3940             # treat as left right arrow if unshifted, tab/shift tab if shifted. 
3941             if event
.ShiftDown(): 
3942                 if keycode 
in (wx
.WXK_DOWN
, wx
.WXK_RIGHT
): 
3943                     # remove "shifting" and treat as (forward) tab: 
3944                     event
.m_shiftDown 
= False 
3945                 keep_processing 
= self
._OnChangeField
(event
) 
3947                 keep_processing 
= self
._OnArrow
(event
) 
3948         # else some other key; keep processing the key 
3950 ##        dbg('keep processing?', keep_processing, indent=0) 
3951         return keep_processing
 
3954     def _OnAutoSelect(self
, field
, match_index 
= None): 
3956         Function called if autoselect feature is enabled and entire control 
3959 ##        dbg('MaskedEditMixin::OnAutoSelect', field._index) 
3960         if match_index 
is not None: 
3961             field
._autoCompleteIndex 
= match_index
 
3964     def _autoComplete(self
, direction
, choices
, value
, compareNoCase
, current_index
): 
3966         This function gets called in response to Auto-complete events. 
3967         It attempts to find a match to the specified value against the 
3968         list of choices; if exact match, the index of then next 
3969         appropriate value in the list, based on the given direction. 
3970         If not an exact match, it will return the index of the 1st value from 
3971         the choice list for which the partial value can be extended to match. 
3972         If no match found, it will return None. 
3973         The function returns a 2-tuple, with the 2nd element being a boolean 
3974         that indicates if partial match was necessary. 
3976 ##        dbg('autoComplete(direction=', direction, 'choices=',choices, 'value=',value,'compareNoCase?', compareNoCase, 'current_index:', current_index, indent=1) 
3978 ##            dbg('nothing to match against', indent=0) 
3979             return (None, False) 
3981         partial_match 
= False 
3984             value 
= value
.lower() 
3986         last_index 
= len(choices
) - 1 
3987         if value 
in choices
: 
3988 ##            dbg('"%s" in', choices) 
3989             if current_index 
is not None and choices
[current_index
] == value
: 
3990                 index 
= current_index
 
3992                 index 
= choices
.index(value
) 
3994 ##            dbg('matched "%s" (%d)' % (choices[index], index)) 
3996 ##                dbg('going to previous') 
3997                 if index 
== 0: index 
= len(choices
) - 1 
4000                 if index 
== len(choices
) - 1: index 
= 0 
4002 ##            dbg('change value to "%s" (%d)' % (choices[index], index)) 
4005             partial_match 
= True 
4006             value 
= value
.strip() 
4007 ##            dbg('no match; try to auto-complete:') 
4009 ##            dbg('searching for "%s"' % value) 
4010             if current_index 
is None: 
4011                 indices 
= range(len(choices
)) 
4016                     indices 
= range(current_index 
+1, len(choices
)) + range(current_index
+1) 
4017 ##                    dbg('range(current_index+1 (%d), len(choices) (%d)) + range(%d):' % (current_index+1, len(choices), current_index+1), indices) 
4019                     indices 
= range(current_index
-1, -1, -1) + range(len(choices
)-1, current_index
-1, -1) 
4020 ##                    dbg('range(current_index-1 (%d), -1) + range(len(choices)-1 (%d)), current_index-1 (%d):' % (current_index-1, len(choices)-1, current_index-1), indices) 
4021 ####            dbg('indices:', indices) 
4022             for index 
in indices
: 
4023                 choice 
= choices
[index
] 
4024                 if choice
.find(value
, 0) == 0: 
4025 ##                    dbg('match found:', choice) 
4029 ##                    dbg('choice: "%s" - no match' % choice) 
4031             if match 
is not None: 
4032 ##                dbg('matched', match) 
4035 ##                dbg('no match found') 
4038         return (match
, partial_match
) 
4041     def _AdjustField(self
, pos
): 
4043         This function gets called by default whenever the cursor leaves a field. 
4044         The pos argument given is the char position before leaving that field. 
4045         By default, floating point, integer and date values are adjusted to be 
4046         legal in this function.  Derived classes may override this function 
4047         to modify the value of the control in a different way when changing fields. 
4049         NOTE: these change the value immediately, and restore the cursor to 
4050         the passed location, so that any subsequent code can then move it 
4051         based on the operation being performed. 
4053         newvalue 
= value 
= self
._GetValue
() 
4054         field 
= self
._FindField
(pos
) 
4055         start
, end
, slice = self
._FindFieldExtent
(getslice
=True) 
4056         newfield 
= field
._AdjustField
(slice) 
4057         newvalue 
= value
[:start
] + newfield 
+ value
[end
:] 
4059         if self
._isFloat 
and newvalue 
!= self
._template
: 
4060             newvalue 
= self
._adjustFloat
(newvalue
) 
4062         if self
._ctrl
_constraints
._isInt 
and value 
!= self
._template
: 
4063             newvalue 
= self
._adjustInt
(value
) 
4065         if self
._isDate 
and value 
!= self
._template
: 
4066             newvalue 
= self
._adjustDate
(value
, fixcentury
=True) 
4067             if self
._4digityear
: 
4068                 year2dig 
= self
._dateExtent 
- 2 
4069                 if pos 
== year2dig 
and value
[year2dig
] != newvalue
[year2dig
]: 
4072         if newvalue 
!= value
: 
4073 ##            dbg('old value: "%s"\nnew value: "%s"' % (value, newvalue)) 
4074             self
._SetValue
(newvalue
) 
4075             self
._SetInsertionPoint
(pos
) 
4078     def _adjustKey(self
, pos
, key
): 
4079         """ Apply control formatting to the key (e.g. convert to upper etc). """ 
4080         field 
= self
._FindField
(pos
) 
4081         if field
._forceupper 
and key 
in range(97,123): 
4082             key 
= ord( chr(key
).upper()) 
4084         if field
._forcelower 
and key 
in range(97,123): 
4085             key 
= ord( chr(key
).lower()) 
4090     def _adjustPos(self
, pos
, key
): 
4092         Checks the current insertion point position and adjusts it if 
4093         necessary to skip over non-editable characters. 
4095 ##        dbg('_adjustPos', pos, key, indent=1) 
4096         sel_start
, sel_to 
= self
._GetSelection
() 
4097         # If a numeric or decimal mask, and negatives allowed, reserve the 
4098         # first space for sign, and last one if using parens. 
4100             and ((pos 
== self
._signpos 
and key 
in (ord('-'), ord('+'), ord(' ')) ) 
4101                  or self
._useParens 
and pos 
== self
._masklength 
-1)): 
4102 ##            dbg('adjusted pos:', pos, indent=0) 
4105         if key 
not in self
._nav
: 
4106             field 
= self
._FindField
(pos
) 
4108 ##            dbg('field._insertRight?', field._insertRight) 
4109             if field
._insertRight
:              # if allow right-insert 
4110                 start
, end 
= field
._extent
 
4111                 slice = self
._GetValue
()[start
:end
].strip() 
4112                 field_len 
= end 
- start
 
4113                 if pos 
== end
:                      # if cursor at right edge of field 
4114                     # if not filled or supposed to stay in field, keep current position 
4115 ####                    dbg('pos==end') 
4116 ####                    dbg('len (slice):', len(slice)) 
4117 ####                    dbg('field_len?', field_len) 
4118 ####                    dbg('pos==end; len (slice) < field_len?', len(slice) < field_len) 
4119 ####                    dbg('not field._moveOnFieldFull?', not field._moveOnFieldFull) 
4120                     if len(slice) == field_len 
and field
._moveOnFieldFull
: 
4121                         # move cursor to next field: 
4122                         pos 
= self
._findNextEntry
(pos
) 
4123                         self
._SetInsertionPoint
(pos
) 
4125                             self
._SetSelection
(pos
, sel_to
)     # restore selection 
4127                             self
._SetSelection
(pos
, pos
)        # remove selection 
4128                     else: # leave cursor alone 
4131                     # if at start of control, move to right edge 
4132                     if (sel_to 
== sel_start
 
4133                         and (self
._isTemplateChar
(pos
) or (pos 
== start 
and len(slice)+ 1 < field_len
)) 
4135                         pos 
= end                   
# move to right edge 
4136 ##                    elif sel_start <= start and sel_to == end: 
4137 ##                        # select to right edge of field - 1 (to replace char) 
4139 ##                        self._SetInsertionPoint(pos) 
4140 ##                        # restore selection 
4141 ##                        self._SetSelection(sel_start, pos) 
4143                     elif self
._signOk 
and sel_start 
== 0:   # if selected to beginning and signed, 
4144                         # adjust to past reserved sign position: 
4145                         pos 
= self
._fields
[0]._extent
[0] 
4146                         self
._SetInsertionPoint
(pos
) 
4148                         self
._SetSelection
(pos
, sel_to
) 
4150                         pass    # leave position/selection alone 
4152             # else make sure the user is not trying to type over a template character 
4153             # If they are, move them to the next valid entry position 
4154             elif self
._isTemplateChar
(pos
): 
4155                 if( not field
._moveOnFieldFull
 
4156                       and (not self
._signOk
 
4158                                and field
._index 
== 0 
4159                                and pos 
> 0) ) ):      # don't move to next field without explicit cursor movement 
4162                     # find next valid position 
4163                     pos 
= self
._findNextEntry
(pos
) 
4164                     self
._SetInsertionPoint
(pos
) 
4165                     if pos 
< sel_to
:    # restore selection 
4166                         self
._SetSelection
(pos
, sel_to
) 
4168                         self
._SetSelection
(pos
, pos
) 
4169 ##        dbg('adjusted pos:', pos, indent=0) 
4173     def _adjustFloat(self
, candidate
=None): 
4175         'Fixes' an floating point control. Collapses spaces, right-justifies, etc. 
4177 ##        dbg('MaskedEditMixin::_adjustFloat, candidate = "%s"' % candidate, indent=1) 
4178         lenInt
,lenFraction  
= [len(s
) for s 
in self
._mask
.split('.')]  ## Get integer, fraction lengths 
4180         if candidate 
is None: value 
= self
._GetValue
() 
4181         else: value 
= candidate
 
4182 ##        dbg('value = "%(value)s"' % locals(), 'len(value):', len(value)) 
4183         intStr
, fracStr 
= value
.split(self
._decimalChar
) 
4185         intStr 
= self
._fields
[0]._AdjustField
(intStr
) 
4186 ##        dbg('adjusted intStr: "%s"' % intStr) 
4187         lenInt 
= len(intStr
) 
4188         fracStr 
= fracStr 
+ ('0'*(lenFraction
-len(fracStr
)))  # add trailing spaces to decimal 
4190 ##        dbg('intStr "%(intStr)s"' % locals()) 
4191 ##        dbg('lenInt:', lenInt) 
4193         intStr 
= string
.rjust( intStr
[-lenInt
:], lenInt
) 
4194 ##        dbg('right-justifed intStr = "%(intStr)s"' % locals()) 
4195         newvalue 
= intStr 
+ self
._decimalChar 
+ fracStr
 
4198             if len(newvalue
) < self
._masklength
: 
4199                 newvalue 
= ' ' + newvalue
 
4200             signedvalue 
= self
._getSignedValue
(newvalue
)[0] 
4201             if signedvalue 
is not None: newvalue 
= signedvalue
 
4203         # Finally, align string with decimal position, left-padding with 
4205         newdecpos 
= newvalue
.find(self
._decimalChar
) 
4206         if newdecpos 
< self
._decimalpos
: 
4207             padlen 
= self
._decimalpos 
- newdecpos
 
4208             newvalue 
= string
.join([' ' * padlen
] + [newvalue
] ,'') 
4210         if self
._signOk 
and self
._useParens
: 
4211             if newvalue
.find('(') != -1: 
4212                 newvalue 
= newvalue
[:-1] + ')' 
4214                 newvalue 
= newvalue
[:-1] + ' ' 
4216 ##        dbg('newvalue = "%s"' % newvalue) 
4217         if candidate 
is None: 
4218             wx
.CallAfter(self
._SetValue
, newvalue
) 
4223     def _adjustInt(self
, candidate
=None): 
4224         """ 'Fixes' an integer control. Collapses spaces, right or left-justifies.""" 
4225 ##        dbg("MaskedEditMixin::_adjustInt", candidate) 
4226         lenInt 
= self
._masklength
 
4227         if candidate 
is None: value 
= self
._GetValue
() 
4228         else: value 
= candidate
 
4230         intStr 
= self
._fields
[0]._AdjustField
(value
) 
4231         intStr 
= intStr
.strip() # drop extra spaces 
4232 ##        dbg('adjusted field: "%s"' % intStr) 
4234         if self
._isNeg 
and intStr
.find('-') == -1 and intStr
.find('(') == -1: 
4236                 intStr 
= '(' + intStr 
+ ')' 
4238                 intStr 
= '-' + intStr
 
4239         elif self
._isNeg 
and intStr
.find('-') != -1 and self
._useParens
: 
4240             intStr 
= intStr
.replace('-', '(') 
4242         if( self
._signOk 
and ((self
._useParens 
and intStr
.find('(') == -1) 
4243                                 or (not self
._useParens 
and intStr
.find('-') == -1))): 
4244             intStr 
= ' ' + intStr
 
4246                 intStr 
+= ' '   # space for right paren position 
4248         elif self
._signOk 
and self
._useParens 
and intStr
.find('(') != -1 and intStr
.find(')') == -1: 
4249             # ensure closing right paren: 
4252         if self
._fields
[0]._alignRight
:     ## Only if right-alignment is enabled 
4253             intStr 
= intStr
.rjust( lenInt 
) 
4255             intStr 
= intStr
.ljust( lenInt 
) 
4257         if candidate 
is None: 
4258             wx
.CallAfter(self
._SetValue
, intStr 
) 
4262     def _adjustDate(self
, candidate
=None, fixcentury
=False, force4digit_year
=False): 
4264         'Fixes' a date control, expanding the year if it can. 
4265         Applies various self-formatting options. 
4267 ##        dbg("MaskedEditMixin::_adjustDate", indent=1) 
4268         if candidate 
is None: text    
= self
._GetValue
() 
4269         else: text 
= candidate
 
4270 ##        dbg('text=', text) 
4271         if self
._datestyle 
== "YMD": 
4276 ##        dbg('getYear: "%s"' % _getYear(text, self._datestyle)) 
4277         year    
= string
.replace( _getYear( text
, self
._datestyle
),self
._fields
[year_field
]._fillChar
,"")  # drop extra fillChars 
4278         month   
= _getMonth( text
, self
._datestyle
) 
4279         day     
= _getDay( text
, self
._datestyle
) 
4280 ##        dbg('self._datestyle:', self._datestyle, 'year:', year, 'Month', month, 'day:', day) 
4283         yearstart 
= self
._dateExtent 
- 4 
4287                  or (self
._GetInsertionPoint
() > yearstart
+1 and text
[yearstart
+2] == ' ') 
4288                  or (self
._GetInsertionPoint
() > yearstart
+2 and text
[yearstart
+3] == ' ') ) ): 
4289             ## user entered less than four digits and changing fields or past point where we could 
4290             ## enter another digit: 
4294 ##                dbg('bad year=', year) 
4295                 year 
= text
[yearstart
:self
._dateExtent
] 
4297         if len(year
) < 4 and yearVal
: 
4299                 # Fix year adjustment to be less "20th century" :-) and to adjust heuristic as the 
4301                 now 
= wx
.DateTime_Now() 
4302                 century 
= (now
.GetYear() /100) * 100        # "this century" 
4303                 twodig_year 
= now
.GetYear() - century       
# "this year" (2 digits) 
4304                 # if separation between today's 2-digit year and typed value > 50, 
4305                 #      assume last century, 
4306                 # else assume this century. 
4308                 # Eg: if 2003 and yearVal == 30, => 2030 
4309                 #     if 2055 and yearVal == 80, => 2080 
4310                 #     if 2010 and yearVal == 96, => 1996 
4312                 if abs(yearVal 
- twodig_year
) > 50: 
4313                     yearVal 
= (century 
- 100) + yearVal
 
4315                     yearVal 
= century 
+ yearVal
 
4316                 year 
= str( yearVal 
) 
4317             else:   # pad with 0's to make a 4-digit year 
4318                 year 
= "%04d" % yearVal
 
4319             if self
._4digityear 
or force4digit_year
: 
4320                 text 
= _makeDate(year
, month
, day
, self
._datestyle
, text
) + text
[self
._dateExtent
:] 
4321 ##        dbg('newdate: "%s"' % text, indent=0) 
4325     def _goEnd(self
, getPosOnly
=False): 
4326         """ Moves the insertion point to the end of user-entry """ 
4327 ##        dbg("MaskedEditMixin::_goEnd; getPosOnly:", getPosOnly, indent=1) 
4328         text 
= self
._GetValue
() 
4329 ####        dbg('text: "%s"' % text) 
4331         if len(text
.rstrip()): 
4332             for i 
in range( min( self
._masklength
-1, len(text
.rstrip())), -1, -1): 
4333 ####                dbg('i:', i, 'self._isMaskChar(%d)' % i, self._isMaskChar(i)) 
4334                 if self
._isMaskChar
(i
): 
4336 ####                    dbg("text[%d]: '%s'" % (i, char)) 
4342             pos 
= self
._goHome
(getPosOnly
=True) 
4344             pos 
= min(i
,self
._masklength
) 
4346         field 
= self
._FindField
(pos
) 
4347         start
, end 
= field
._extent
 
4348         if field
._insertRight 
and pos 
< end
: 
4350 ##        dbg('next pos:', pos) 
4355             self
._SetInsertionPoint
(pos
) 
4358     def _goHome(self
, getPosOnly
=False): 
4359         """ Moves the insertion point to the beginning of user-entry """ 
4360 ##        dbg("MaskedEditMixin::_goHome; getPosOnly:", getPosOnly, indent=1) 
4361         text 
= self
._GetValue
() 
4362         for i 
in range(self
._masklength
): 
4363             if self
._isMaskChar
(i
): 
4370             self
._SetInsertionPoint
(max(i
,0)) 
4374     def _getAllowedChars(self
, pos
): 
4375         """ Returns a string of all allowed user input characters for the provided 
4376             mask character plus control options 
4378         maskChar 
= self
.maskdict
[pos
] 
4379         okchars 
= self
.maskchardict
[maskChar
]    ## entry, get mask approved characters 
4381         # convert okchars to unicode if required; will force subsequent appendings to 
4382         # result in unicode strings 
4383         if 'unicode' in wx
.PlatformInfo 
and type(okchars
) != types
.UnicodeType
: 
4384             okchars 
= okchars
.decode(self
._defaultEncoding
) 
4386         field 
= self
._FindField
(pos
) 
4387         if okchars 
and field
._okSpaces
:          ## Allow spaces? 
4389         if okchars 
and field
._includeChars
:      ## any additional included characters? 
4390             okchars 
+= field
._includeChars
 
4391 ####        dbg('okchars[%d]:' % pos, okchars) 
4395     def _isMaskChar(self
, pos
): 
4396         """ Returns True if the char at position pos is a special mask character (e.g. NCXaA#) 
4398         if pos 
< self
._masklength
: 
4399             return self
.ismasked
[pos
] 
4404     def _isTemplateChar(self
,Pos
): 
4405         """ Returns True if the char at position pos is a template character (e.g. -not- NCXaA#) 
4407         if Pos 
< self
._masklength
: 
4408             return not self
._isMaskChar
(Pos
) 
4413     def _isCharAllowed(self
, char
, pos
, checkRegex
=False, allowAutoSelect
=True, ignoreInsertRight
=False): 
4414         """ Returns True if character is allowed at the specific position, otherwise False.""" 
4415 ##        dbg('_isCharAllowed', char, pos, checkRegex, indent=1) 
4416         field 
= self
._FindField
(pos
) 
4417         right_insert 
= False 
4419         if self
.controlInitialized
: 
4420             sel_start
, sel_to 
= self
._GetSelection
() 
4422             sel_start
, sel_to 
= pos
, pos
 
4424         if (field
._insertRight 
or self
._ctrl
_constraints
._insertRight
) and not ignoreInsertRight
: 
4425             start
, end 
= field
._extent
 
4426             field_len 
= end 
- start
 
4427             if self
.controlInitialized
: 
4428                 value 
= self
._GetValue
() 
4429                 fstr 
= value
[start
:end
].strip() 
4431                     while fstr 
and fstr
[0] == '0': 
4433                 input_len 
= len(fstr
) 
4434                 if self
._signOk 
and '-' in fstr 
or '(' in fstr
: 
4435                     input_len 
-= 1  # sign can move out of field, so don't consider it in length 
4437                 value 
= self
._template
 
4438                 input_len 
= 0   # can't get the current "value", so use 0 
4441             # if entire field is selected or position is at end and field is not full, 
4442             # or if allowed to right-insert at any point in field and field is not full and cursor is not at a fillChar 
4443             # or the field is a singleton integer field and is currently 0 and we're at the end: 
4444             if( (sel_start
, sel_to
) == field
._extent
 
4445                 or (pos 
== end 
and ((input_len 
< field_len
) 
4447                                          and input_len 
== field_len
 
4449                                          and value
[end
-1] == '0' 
4453 ##                dbg('pos = end - 1 = ', pos, 'right_insert? 1') 
4455             elif( field
._allowInsert 
and sel_start 
== sel_to
 
4456                   and (sel_to 
== end 
or (sel_to 
< self
._masklength 
and value
[sel_start
] != field
._fillChar
)) 
4457                   and input_len 
< field_len 
): 
4458                 pos 
= sel_to 
- 1    # where character will go 
4459 ##                dbg('pos = sel_to - 1 = ', pos, 'right_insert? 1') 
4461             # else leave pos alone... 
4463 ##                dbg('pos stays ', pos, 'right_insert? 0') 
4466         if self
._isTemplateChar
( pos 
):  ## if a template character, return empty 
4467 ##            dbg('%d is a template character; returning False' % pos, indent=0) 
4470         if self
._isMaskChar
( pos 
): 
4471             okChars  
= self
._getAllowedChars
(pos
) 
4473             if self
._fields
[0]._groupdigits 
and (self
._isInt 
or (self
._isFloat 
and pos 
< self
._decimalpos
)): 
4474                 okChars 
+= self
._fields
[0]._groupChar
 
4477                 if self
._isInt 
or (self
._isFloat 
and pos 
< self
._decimalpos
): 
4481                 elif self
._useParens 
and (self
._isInt 
or (self
._isFloat 
and pos 
> self
._decimalpos
)): 
4484 ####            dbg('%s in %s?' % (char, okChars), char in okChars) 
4485             approved 
= char 
in okChars
 
4487             if approved 
and checkRegex
: 
4488 ##                dbg("checking appropriate regex's") 
4489                 value 
= self
._eraseSelection
(self
._GetValue
()) 
4491                     # move the position to the right side of the insertion: 
4496                     newvalue
, ignore
, ignore
, ignore
, ignore 
= self
._insertKey
(char
, at
, sel_start
, sel_to
, value
, allowAutoSelect
=True) 
4498                     newvalue
, ignore 
= self
._insertKey
(char
, at
, sel_start
, sel_to
, value
) 
4499 ##                dbg('newvalue: "%s"' % newvalue) 
4501                 fields 
= [self
._FindField
(pos
)] + [self
._ctrl
_constraints
] 
4502                 for field 
in fields
:    # includes fields[-1] == "ctrl_constraints" 
4503                     if field
._regexMask 
and field
._filter
: 
4504 ##                        dbg('checking vs. regex') 
4505                         start
, end 
= field
._extent
 
4506                         slice = newvalue
[start
:end
] 
4507                         approved 
= (re
.match( field
._filter
, slice) is not None) 
4508 ##                        dbg('approved?', approved) 
4509                     if not approved
: break 
4513 ##            dbg('%d is a !???! character; returning False', indent=0) 
4517     def _applyFormatting(self
): 
4518         """ Apply formatting depending on the control's state. 
4519             Need to find a way to call this whenever the value changes, in case the control's 
4520             value has been changed or set programatically. 
4523 ##        dbg('MaskedEditMixin::_applyFormatting', indent=1) 
4525         # Handle negative numbers 
4527             text
, signpos
, right_signpos 
= self
._getSignedValue
() 
4528 ##            dbg('text: "%s", signpos:' % text, signpos) 
4529             if not text 
or text
[signpos
] not in ('-','('): 
4531 ##                dbg('no valid sign found; new sign:', self._isNeg) 
4532                 if text 
and signpos 
!= self
._signpos
: 
4533                     self
._signpos 
= signpos
 
4534             elif text 
and self
._valid 
and not self
._isNeg 
and text
[signpos
] in ('-', '('): 
4535 ##                dbg('setting _isNeg to True') 
4537 ##            dbg('self._isNeg:', self._isNeg) 
4539         if self
._signOk 
and self
._isNeg
: 
4540             fc 
= self
._signedForegroundColour
 
4542             fc 
= self
._foregroundColour
 
4544         if hasattr(fc
, '_name'): 
4548 ##        dbg('setting foreground to', c) 
4549         self
.SetForegroundColour(fc
) 
4554                 bc 
= self
._emptyBackgroundColour
 
4556                 bc 
= self
._validBackgroundColour
 
4559             bc 
= self
._invalidBackgroundColour
 
4560         if hasattr(bc
, '_name'): 
4564 ##        dbg('setting background to', c) 
4565         self
.SetBackgroundColour(bc
) 
4567 ##        dbg(indent=0, suspend=0) 
4570     def _getAbsValue(self
, candidate
=None): 
4571         """ Return an unsigned value (i.e. strip the '-' prefix if any), and sign position(s). 
4573 ##        dbg('MaskedEditMixin::_getAbsValue; candidate="%s"' % candidate, indent=1) 
4574         if candidate 
is None: text 
= self
._GetValue
() 
4575         else: text 
= candidate
 
4576         right_signpos 
= text
.find(')') 
4579             if self
._ctrl
_constraints
._alignRight 
and self
._fields
[0]._fillChar 
== ' ': 
4580                 signpos 
= text
.find('-') 
4582 ##                    dbg('no - found; searching for (') 
4583                     signpos 
= text
.find('(') 
4585 ##                    dbg('- found at', signpos) 
4589 ##                    dbg('signpos still -1') 
4590 ##                    dbg('len(%s) (%d) < len(%s) (%d)?' % (text, len(text), self._mask, self._masklength), len(text) < self._masklength) 
4591                     if len(text
) < self
._masklength
: 
4593                     if len(text
) < self
._masklength
: 
4595                     if len(text
) > self
._masklength 
and text
[-1] in (')', ' '): 
4598 ##                        dbg('len(%s) (%d), len(%s) (%d)' % (text, len(text), self._mask, self._masklength)) 
4599 ##                        dbg('len(%s) - (len(%s) + 1):' % (text, text.lstrip()) , len(text) - (len(text.lstrip()) + 1)) 
4600                         signpos 
= len(text
) - (len(text
.lstrip()) + 1) 
4602                         if self
._useParens 
and not text
.strip(): 
4603                             signpos 
-= 1    # empty value; use penultimate space 
4604 ##                dbg('signpos:', signpos) 
4606                     text 
= text
[:signpos
] + ' ' + text
[signpos
+1:] 
4611                     text 
= self
._template
[0] + text
[1:] 
4615             if right_signpos 
!= -1: 
4617                     text 
= text
[:right_signpos
] + ' ' + text
[right_signpos
+1:] 
4618                 elif len(text
) > self
._masklength
: 
4619                     text 
= text
[:right_signpos
] + text
[right_signpos
+1:] 
4623             elif self
._useParens 
and self
._signOk
: 
4624                 # figure out where it ought to go: 
4625                 right_signpos 
= self
._masklength 
- 1     # initial guess 
4626                 if not self
._ctrl
_constraints
._alignRight
: 
4627 ##                    dbg('not right-aligned') 
4628                     if len(text
.strip()) == 0: 
4629                         right_signpos 
= signpos 
+ 1 
4630                     elif len(text
.strip()) < self
._masklength
: 
4631                         right_signpos 
= len(text
.rstrip()) 
4632 ##                dbg('right_signpos:', right_signpos) 
4634             groupchar 
= self
._fields
[0]._groupChar
 
4636                 value 
= long(text
.replace(groupchar
,'').replace('(','-').replace(')','').replace(' ', '')) 
4638 ##                dbg('invalid number', indent=0) 
4639                 return None, signpos
, right_signpos
 
4643                 groupchar 
= self
._fields
[0]._groupChar
 
4644                 value 
= float(text
.replace(groupchar
,'').replace(self
._decimalChar
, '.').replace('(', '-').replace(')','').replace(' ', '')) 
4645 ##                dbg('value:', value) 
4649             if value 
< 0 and value 
is not None: 
4650                 signpos 
= text
.find('-') 
4652                     signpos 
= text
.find('(') 
4654                 text 
= text
[:signpos
] + self
._template
[signpos
] + text
[signpos
+1:] 
4656                 # look forwards up to the decimal point for the 1st non-digit 
4657 ##                dbg('decimal pos:', self._decimalpos) 
4658 ##                dbg('text: "%s"' % text) 
4660                     signpos 
= self
._decimalpos 
- (len(text
[:self
._decimalpos
].lstrip()) + 1) 
4661                     # prevent checking for empty string - Tomo - Wed 14 Jan 2004 03:19:09 PM CET 
4662                     if len(text
) >= signpos
+1 and  text
[signpos
+1] in ('-','('): 
4666 ##                dbg('signpos:', signpos) 
4670                     right_signpos 
= self
._masklength 
- 1 
4671                     text 
= text
[:right_signpos
] + ' ' 
4672                     if text
[signpos
] == '(': 
4673                         text 
= text
[:signpos
] + ' ' + text
[signpos
+1:] 
4675                     right_signpos 
= text
.find(')') 
4676                     if right_signpos 
!= -1: 
4681 ##                dbg('invalid number') 
4684 ##        dbg('abstext = "%s"' % text, 'signpos:', signpos, 'right_signpos:', right_signpos) 
4686         return text
, signpos
, right_signpos
 
4689     def _getSignedValue(self
, candidate
=None): 
4690         """ Return a signed value by adding a "-" prefix if the value 
4691             is set to negative, or a space if positive. 
4693 ##        dbg('MaskedEditMixin::_getSignedValue; candidate="%s"' % candidate, indent=1) 
4694         if candidate 
is None: text 
= self
._GetValue
() 
4695         else: text 
= candidate
 
4698         abstext
, signpos
, right_signpos 
= self
._getAbsValue
(text
) 
4702                 return abstext
, signpos
, right_signpos
 
4704             if self
._isNeg 
or text
[signpos
] in ('-', '('): 
4711             if abstext
[signpos
] not in string
.digits
: 
4712                 text 
= abstext
[:signpos
] + sign 
+ abstext
[signpos
+1:] 
4714                 # this can happen if value passed is too big; sign assumed to be 
4715                 # in position 0, but if already filled with a digit, prepend sign... 
4716                 text 
= sign 
+ abstext
 
4717             if self
._useParens 
and text
.find('(') != -1: 
4718                 text 
= text
[:right_signpos
] + ')' + text
[right_signpos
+1:] 
4721 ##        dbg('signedtext = "%s"' % text, 'signpos:', signpos, 'right_signpos', right_signpos) 
4723         return text
, signpos
, right_signpos
 
4726     def GetPlainValue(self
, candidate
=None): 
4727         """ Returns control's value stripped of the template text. 
4728             plainvalue = MaskedEditMixin.GetPlainValue() 
4730 ##        dbg('MaskedEditMixin::GetPlainValue; candidate="%s"' % candidate, indent=1) 
4732         if candidate 
is None: text 
= self
._GetValue
() 
4733         else: text 
= candidate
 
4736 ##            dbg('returned ""', indent=0) 
4740             for idx 
in range( min(len(self
._template
), len(text
)) ): 
4741                 if self
._mask
[idx
] in maskchars
: 
4744             if self
._isFloat 
or self
._isInt
: 
4745 ##                dbg('plain so far: "%s"' % plain) 
4746                 plain 
= plain
.replace('(', '-').replace(')', ' ') 
4747 ##                dbg('plain after sign regularization: "%s"' % plain) 
4749                 if self
._signOk 
and self
._isNeg 
and plain
.count('-') == 0: 
4750                     # must be in reserved position; add to "plain value" 
4751                     plain 
= '-' + plain
.strip() 
4753                 if self
._fields
[0]._alignRight
: 
4754                     lpad 
= plain
.count(',') 
4755                     plain 
= ' ' * lpad 
+ plain
.replace(',','') 
4757                     plain 
= plain
.replace(',','') 
4758 ##                dbg('plain after pad and group:"%s"' % plain) 
4760 ##            dbg('returned "%s"' % plain.rstrip(), indent=0) 
4761             return plain
.rstrip() 
4764     def IsEmpty(self
, value
=None): 
4766         Returns True if control is equal to an empty value. 
4767         (Empty means all editable positions in the template == fillChar.) 
4769         if value 
is None: value 
= self
._GetValue
() 
4770         if value 
== self
._template 
and not self
._defaultValue
: 
4771 ####            dbg("IsEmpty? 1 (value == self._template and not self._defaultValue)") 
4772             return True     # (all mask chars == fillChar by defn) 
4773         elif value 
== self
._template
: 
4775             for pos 
in range(len(self
._template
)): 
4776 ####                dbg('isMaskChar(%(pos)d)?' % locals(), self._isMaskChar(pos)) 
4777 ####                dbg('value[%(pos)d] != self._fillChar?' %locals(), value[pos] != self._fillChar[pos]) 
4778                 if self
._isMaskChar
(pos
) and value
[pos
] not in (' ', self
._fillChar
[pos
]): 
4780 ####            dbg("IsEmpty? %(empty)d (do all mask chars == fillChar?)" % locals()) 
4783 ####            dbg("IsEmpty? 0 (value doesn't match template)") 
4787     def IsDefault(self
, value
=None): 
4789         Returns True if the value specified (or the value of the control if not specified) 
4790         is equal to the default value. 
4792         if value 
is None: value 
= self
._GetValue
() 
4793         return value 
== self
._template
 
4796     def IsValid(self
, value
=None): 
4797         """ Indicates whether the value specified (or the current value of the control 
4798         if not specified) is considered valid.""" 
4799 ####        dbg('MaskedEditMixin::IsValid("%s")' % value, indent=1) 
4800         if value 
is None: value 
= self
._GetValue
() 
4801         ret 
= self
._CheckValid
(value
) 
4806     def _eraseSelection(self
, value
=None, sel_start
=None, sel_to
=None): 
4807         """ Used to blank the selection when inserting a new character. """ 
4808 ##        dbg("MaskedEditMixin::_eraseSelection", indent=1) 
4809         if value 
is None: value 
= self
._GetValue
() 
4810         if sel_start 
is None or sel_to 
is None: 
4811             sel_start
, sel_to 
= self
._GetSelection
()                   ## check for a range of selected text 
4812 ##        dbg('value: "%s"' % value) 
4813 ##        dbg("current sel_start, sel_to:", sel_start, sel_to) 
4815         newvalue 
= list(value
) 
4816         for i 
in range(sel_start
, sel_to
): 
4817             if self
._signOk 
and newvalue
[i
] in ('-', '(', ')'): 
4818 ##                dbg('found sign (%s) at' % newvalue[i], i) 
4820                 # balance parentheses: 
4821                 if newvalue
[i
] == '(': 
4822                     right_signpos 
= value
.find(')') 
4823                     if right_signpos 
!= -1: 
4824                         newvalue
[right_signpos
] = ' ' 
4826                 elif newvalue
[i
] == ')': 
4827                     left_signpos 
= value
.find('(') 
4828                     if left_signpos 
!= -1: 
4829                         newvalue
[left_signpos
] = ' ' 
4833             elif self
._isMaskChar
(i
): 
4834                 field 
= self
._FindField
(i
) 
4838                     newvalue
[i
] = self
._template
[i
] 
4840         value 
= string
.join(newvalue
,"") 
4841 ##        dbg('new value: "%s"' % value) 
4846     def _insertKey(self
, char
, pos
, sel_start
, sel_to
, value
, allowAutoSelect
=False): 
4847         """ Handles replacement of the character at the current insertion point.""" 
4848 ##        dbg('MaskedEditMixin::_insertKey', "\'" + char + "\'", pos, sel_start, sel_to, '"%s"' % value, indent=1) 
4850         text 
= self
._eraseSelection
(value
) 
4851         field 
= self
._FindField
(pos
) 
4852         start
, end 
= field
._extent
 
4856         # if >= 2 chars selected in a right-insert field, do appropriate erase on field, 
4857         # then set selection to end, and do usual right insert. 
4858         if sel_start 
!= sel_to 
and sel_to 
>= sel_start
+2: 
4859             field 
= self
._FindField
(sel_start
) 
4860             if( field
._insertRight                          
# if right-insert 
4861                 and field
._allowInsert                      
# and allow insert at any point in field 
4862                 and field 
== self
._FindField
(sel_to
) ):     # and selection all in same field 
4863                 text 
= self
._OnErase
(just_return_value
=True)    # remove selection before insert 
4864 ##                dbg('text after (left)erase: "%s"' % text) 
4865                 pos 
= sel_start 
= sel_to
 
4867         if pos 
!= sel_start 
and sel_start 
== sel_to
: 
4868             # adjustpos must have moved the position; make selection match: 
4869             sel_start 
= sel_to 
= pos
 
4871 ##        dbg('field._insertRight?', field._insertRight) 
4872 ##        dbg('field._allowInsert?', field._allowInsert) 
4873 ##        dbg('sel_start, end', sel_start, end) 
4875 ##            dbg('text[sel_start] != field._fillChar?', text[sel_start] != field._fillChar) 
4878         if( field
._insertRight                                  
# field allows right insert 
4879             and ((sel_start
, sel_to
) == field
._extent           
# and whole field selected 
4880                  or (sel_start 
== sel_to                        
# or nothing selected 
4881                      and (sel_start 
== end                      
# and cursor at right edge 
4882                           or (field
._allowInsert                
# or field allows right-insert 
4883                               and sel_start 
< end               
# next to other char in field: 
4884                               and text
[sel_start
] != field
._fillChar
) ) ) ) ): 
4885 ##            dbg('insertRight') 
4886             fstr 
= text
[start
:end
] 
4887             erasable_chars 
= [field
._fillChar
, ' '] 
4889             # if zero padding field, or a single digit, and currently a value of 0, allow erasure of 0: 
4890             if field
._padZero 
or (field
._isInt 
and (end 
- start 
== 1) and fstr
[0] == '0'): 
4891                 erasable_chars
.append('0') 
4894 ####            dbg("fstr[0]:'%s'" % fstr[0]) 
4895 ####            dbg('field_index:', field._index) 
4896 ####            dbg("fstr[0] in erasable_chars?", fstr[0] in erasable_chars) 
4897 ####            dbg("self._signOk and field._index == 0 and fstr[0] in ('-','(')?", self._signOk and field._index == 0 and fstr[0] in ('-','(')) 
4898             if fstr
[0] in erasable_chars 
or (self
._signOk 
and field
._index 
== 0 and fstr
[0] in ('-','(')): 
4900 ####                dbg('value:      "%s"' % text) 
4901 ####                dbg('fstr:       "%s"' % fstr) 
4902 ####                dbg("erased:     '%s'" % erased) 
4903                 field_sel_start 
= sel_start 
- start
 
4904                 field_sel_to 
= sel_to 
- start
 
4905 ##                dbg('left fstr:  "%s"' % fstr[1:field_sel_start]) 
4906 ##                dbg('right fstr: "%s"' % fstr[field_sel_to:end]) 
4907                 fstr 
= fstr
[1:field_sel_start
] + char 
+ fstr
[field_sel_to
:end
] 
4908             if field
._alignRight 
and sel_start 
!= sel_to
: 
4909                 field_len 
= end 
- start
 
4910 ##                pos += (field_len - len(fstr))    # move cursor right by deleted amount 
4912 ##                dbg('setting pos to:', pos) 
4914                     fstr 
= '0' * (field_len 
- len(fstr
)) + fstr
 
4916                     fstr 
= fstr
.rjust(field_len
)   # adjust the field accordingly 
4917 ##            dbg('field str: "%s"' % fstr) 
4919             newtext 
= text
[:start
] + fstr 
+ text
[end
:] 
4920             if erased 
in ('-', '(') and self
._signOk
: 
4921                 newtext 
= erased 
+ newtext
[1:] 
4922 ##            dbg('newtext: "%s"' % newtext) 
4924             if self
._signOk 
and field
._index 
== 0: 
4925                 start 
-= 1             # account for sign position 
4927 ####            dbg('field._moveOnFieldFull?', field._moveOnFieldFull) 
4928 ####            dbg('len(fstr.lstrip()) == end-start?', len(fstr.lstrip()) == end-start) 
4929             if( field
._moveOnFieldFull 
and pos 
== end
 
4930                 and len(fstr
.lstrip()) == end
-start
):   # if field now full 
4931                 newpos 
= self
._findNextEntry
(end
)       #   go to next field 
4933                 newpos 
= pos                            
# else keep cursor at current position 
4936 ##            dbg('not newtext') 
4938 ##                dbg('newpos:', newpos) 
4940             if self
._signOk 
and self
._useParens
: 
4941                 old_right_signpos 
= text
.find(')') 
4943             if field
._allowInsert 
and not field
._insertRight 
and sel_to 
<= end 
and sel_start 
>= start
: 
4944 ##                dbg('inserting within a left-insert-capable field') 
4945                 field_len 
= end 
- start
 
4946                 before 
= text
[start
:sel_start
] 
4947                 after 
= text
[sel_to
:end
].strip() 
4948 ####                dbg("current field:'%s'" % text[start:end]) 
4949 ####                dbg("before:'%s'" % before, "after:'%s'" % after) 
4950                 new_len 
= len(before
) + len(after
) + 1 # (for inserted char) 
4951 ####                dbg('new_len:', new_len) 
4953                 if new_len 
< field_len
: 
4954                     retained 
= after 
+ self
._template
[end
-(field_len
-new_len
):end
] 
4955                 elif new_len 
> end
-start
: 
4956                     retained 
= after
[1:] 
4960                 left 
= text
[0:start
] + before
 
4961 ####                dbg("left:'%s'" % left, "retained:'%s'" % retained) 
4962                 right   
= retained 
+ text
[end
:] 
4965                 right   
= text
[pos
+1:] 
4967             if 'unicode' in wx
.PlatformInfo 
and type(char
) != types
.UnicodeType
: 
4968                 # convert the keyboard constant to a unicode value, to 
4969                 # ensure it can be concatenated into the control value: 
4970                 char 
= char
.decode(self
._defaultEncoding
) 
4972             newtext 
= left 
+ char 
+ right
 
4973 ####            dbg('left:    "%s"' % left) 
4974 ####            dbg('right:   "%s"' % right) 
4975 ####            dbg('newtext: "%s"' % newtext) 
4977             if self
._signOk 
and self
._useParens
: 
4978                 # Balance parentheses: 
4979                 left_signpos 
= newtext
.find('(') 
4981                 if left_signpos 
== -1:     # erased '('; remove ')' 
4982                     right_signpos 
= newtext
.find(')') 
4983                     if right_signpos 
!= -1: 
4984                         newtext 
= newtext
[:right_signpos
] + ' ' + newtext
[right_signpos
+1:] 
4986                 elif old_right_signpos 
!= -1: 
4987                     right_signpos 
= newtext
.find(')') 
4989                     if right_signpos 
== -1: # just replaced right-paren 
4990                         if newtext
[pos
] == ' ': # we just erased '); erase '(' 
4991                             newtext 
= newtext
[:left_signpos
] + ' ' + newtext
[left_signpos
+1:] 
4992                         else:   # replaced with digit; move ') over 
4993                             if self
._ctrl
_constraints
._alignRight 
or self
._isFloat
: 
4994                                 newtext 
= newtext
[:-1] + ')' 
4996                                 rstripped_text 
= newtext
.rstrip() 
4997                                 right_signpos 
= len(rstripped_text
) 
4998 ##                                dbg('old_right_signpos:', old_right_signpos, 'right signpos now:', right_signpos) 
4999                                 newtext 
= newtext
[:right_signpos
] + ')' + newtext
[right_signpos
+1:] 
5001             if( field
._insertRight                                  
# if insert-right field (but we didn't start at right edge) 
5002                 and field
._moveOnFieldFull                          
# and should move cursor when full 
5003                 and len(newtext
[start
:end
].strip()) == end
-start
):  # and field now full 
5004                 newpos 
= self
._findNextEntry
(end
)                   #   go to next field 
5005 ##                dbg('newpos = nextentry =', newpos) 
5007 ##                dbg('pos:', pos, 'newpos:', pos+1) 
5012             new_select_to 
= newpos     
# (default return values) 
5016             if field
._autoSelect
: 
5017                 match_index
, partial_match 
= self
._autoComplete
(1,  # (always forward) 
5018                                                                 field
._compareChoices
, 
5020                                                                 compareNoCase
=field
._compareNoCase
, 
5021                                                                 current_index 
= field
._autoCompleteIndex
-1) 
5022                 if match_index 
is not None and partial_match
: 
5023                     matched_str 
= newtext
[start
:end
] 
5024                     newtext 
= newtext
[:start
] + field
._choices
[match_index
] + newtext
[end
:] 
5027                     if field
._insertRight
: 
5028                         # adjust position to just after partial match in field 
5029                         newpos 
= end 
- (len(field
._choices
[match_index
].strip()) - len(matched_str
.strip())) 
5031             elif self
._ctrl
_constraints
._autoSelect
: 
5032                 match_index
, partial_match 
= self
._autoComplete
( 
5033                                         1,  # (always forward) 
5034                                         self
._ctrl
_constraints
._compareChoices
, 
5036                                         self
._ctrl
_constraints
._compareNoCase
, 
5037                                         current_index 
= self
._ctrl
_constraints
._autoCompleteIndex 
- 1) 
5038                 if match_index 
is not None and partial_match
: 
5039                     matched_str 
= newtext
 
5040                     newtext 
= self
._ctrl
_constraints
._choices
[match_index
] 
5041                     edit_end 
= self
._ctrl
_constraints
._extent
[1] 
5042                     new_select_to 
= min(edit_end
, len(newtext
.rstrip())) 
5043                     match_field 
= self
._ctrl
_constraints
 
5044                     if self
._ctrl
_constraints
._insertRight
: 
5045                         # adjust position to just after partial match in control: 
5046                         newpos 
= self
._masklength 
- (len(self
._ctrl
_constraints
._choices
[match_index
].strip()) - len(matched_str
.strip())) 
5048 ##            dbg('newtext: "%s"' % newtext, 'newpos:', newpos, 'new_select_to:', new_select_to) 
5050             return newtext
, newpos
, new_select_to
, match_field
, match_index
 
5052 ##            dbg('newtext: "%s"' % newtext, 'newpos:', newpos) 
5054             return newtext
, newpos
 
5057     def _OnFocus(self
,event
): 
5059         This event handler is currently necessary to work around new default 
5060         behavior as of wxPython2.3.3; 
5061         The TAB key auto selects the entire contents of the wx.TextCtrl *after* 
5062         the EVT_SET_FOCUS event occurs; therefore we can't query/adjust the selection 
5063         *here*, because it hasn't happened yet.  So to prevent this behavior, and 
5064         preserve the correct selection when the focus event is not due to tab, 
5065         we need to pull the following trick: 
5067 ##        dbg('MaskedEditMixin::_OnFocus') 
5068         if self
.IsBeingDeleted() or self
.GetParent().IsBeingDeleted(): 
5070         wx
.CallAfter(self
._fixSelection
) 
5075     def _CheckValid(self
, candidate
=None): 
5077         This is the default validation checking routine; It verifies that the 
5078         current value of the control is a "valid value," and has the side 
5079         effect of coloring the control appropriately. 
5082 ##        dbg('MaskedEditMixin::_CheckValid: candidate="%s"' % candidate, indent=1) 
5083         oldValid 
= self
._valid
 
5084         if candidate 
is None: value 
= self
._GetValue
() 
5085         else: value 
= candidate
 
5086 ##        dbg('value: "%s"' % value) 
5088         valid 
= True    # assume True 
5090         if not self
.IsDefault(value
) and self
._isDate
:                    ## Date type validation 
5091             valid 
= self
._validateDate
(value
) 
5092 ##            dbg("valid date?", valid) 
5094         elif not self
.IsDefault(value
) and self
._isTime
: 
5095             valid 
= self
._validateTime
(value
) 
5096 ##            dbg("valid time?", valid) 
5098         elif not self
.IsDefault(value
) and (self
._isInt 
or self
._isFloat
):  ## Numeric type 
5099             valid 
= self
._validateNumeric
(value
) 
5100 ##            dbg("valid Number?", valid) 
5102         if valid
:   # and not self.IsDefault(value):    ## generic validation accounts for IsDefault() 
5103             ## valid so far; ensure also allowed by any list or regex provided: 
5104             valid 
= self
._validateGeneric
(value
) 
5105 ##            dbg("valid value?", valid) 
5107 ##        dbg('valid?', valid) 
5111             self
._applyFormatting
() 
5112             if self
._valid 
!= oldValid
: 
5113 ##                dbg('validity changed: oldValid =',oldValid,'newvalid =', self._valid) 
5114 ##                dbg('oldvalue: "%s"' % oldvalue, 'newvalue: "%s"' % self._GetValue()) 
5116 ##        dbg(indent=0, suspend=0) 
5120     def _validateGeneric(self
, candidate
=None): 
5121         """ Validate the current value using the provided list or Regex filter (if any). 
5123         if candidate 
is None: 
5124             text 
= self
._GetValue
() 
5128         valid 
= True    # assume True 
5129         for i 
in [-1] + self
._field
_indices
:   # process global constraints first: 
5130             field 
= self
._fields
[i
] 
5131             start
, end 
= field
._extent
 
5132             slice = text
[start
:end
] 
5133             valid 
= field
.IsValid(slice) 
5140     def _validateNumeric(self
, candidate
=None): 
5141         """ Validate that the value is within the specified range (if specified.)""" 
5142         if candidate 
is None: value 
= self
._GetValue
() 
5143         else: value 
= candidate
 
5145             groupchar 
= self
._fields
[0]._groupChar
 
5147                 number 
= float(value
.replace(groupchar
, '').replace(self
._decimalChar
, '.').replace('(', '-').replace(')', '')) 
5149                 number 
= long( value
.replace(groupchar
, '').replace('(', '-').replace(')', '')) 
5151                     if self
._fields
[0]._alignRight
: 
5152                         require_digit_at 
= self
._fields
[0]._extent
[1]-1 
5154                         require_digit_at 
= self
._fields
[0]._extent
[0] 
5155 ##                    dbg('require_digit_at:', require_digit_at) 
5156 ##                    dbg("value[rda]: '%s'" % value[require_digit_at]) 
5157                     if value
[require_digit_at
] not in list(string
.digits
): 
5161 ##            dbg('number:', number) 
5162             if self
._ctrl
_constraints
._hasRange
: 
5163                 valid 
= self
._ctrl
_constraints
._rangeLow 
<= number 
<= self
._ctrl
_constraints
._rangeHigh
 
5166             groupcharpos 
= value
.rfind(groupchar
) 
5167             if groupcharpos 
!= -1:  # group char present 
5168 ##                dbg('groupchar found at', groupcharpos) 
5169                 if self
._isFloat 
and groupcharpos 
> self
._decimalpos
: 
5170                     # 1st one found on right-hand side is past decimal point 
5171 ##                    dbg('groupchar in fraction; illegal') 
5174                     integer 
= value
[:self
._decimalpos
].strip() 
5176                     integer 
= value
.strip() 
5177 ##                dbg("integer:'%s'" % integer) 
5178                 if integer
[0] in ('-', '('): 
5179                     integer 
= integer
[1:] 
5180                 if integer
[-1] == ')': 
5181                     integer 
= integer
[:-1] 
5183                 parts 
= integer
.split(groupchar
) 
5184 ##                dbg('parts:', parts) 
5185                 for i 
in range(len(parts
)): 
5186                     if i 
== 0 and abs(int(parts
[0])) > 999: 
5187 ##                        dbg('group 0 too long; illegal') 
5190                     elif i 
> 0 and (len(parts
[i
]) != 3 or ' ' in parts
[i
]): 
5191 ##                        dbg('group %i (%s) not right size; illegal' % (i, parts[i])) 
5195 ##            dbg('value not a valid number') 
5200     def _validateDate(self
, candidate
=None): 
5201         """ Validate the current date value using the provided Regex filter. 
5202             Generally used for character types.BufferType 
5204 ##        dbg('MaskedEditMixin::_validateDate', indent=1) 
5205         if candidate 
is None: value 
= self
._GetValue
() 
5206         else: value 
= candidate
 
5207 ##        dbg('value = "%s"' % value) 
5208         text 
= self
._adjustDate
(value
, force4digit_year
=True)     ## Fix the date up before validating it 
5209 ##        dbg('text =', text) 
5210         valid 
= True   # assume True until proven otherwise 
5213             # replace fillChar in each field with space: 
5214             datestr 
= text
[0:self
._dateExtent
] 
5216                 field 
= self
._fields
[i
] 
5217                 start
, end 
= field
._extent
 
5218                 fstr 
= datestr
[start
:end
] 
5219                 fstr
.replace(field
._fillChar
, ' ') 
5220                 datestr 
= datestr
[:start
] + fstr 
+ datestr
[end
:] 
5222             year
, month
, day 
= _getDateParts( datestr
, self
._datestyle
) 
5224 ##            dbg('self._dateExtent:', self._dateExtent) 
5225             if self
._dateExtent 
== 11: 
5226                 month 
= charmonths_dict
[month
.lower()] 
5230 ##            dbg('year, month, day:', year, month, day) 
5233 ##            dbg('cannot convert string to integer parts') 
5236 ##            dbg('cannot convert string to integer month') 
5240             # use wxDateTime to unambiguously try to parse the date: 
5241             # ### Note: because wxDateTime is *brain-dead* and expects months 0-11, 
5242             # rather than 1-12, so handle accordingly: 
5248 ##                    dbg("trying to create date from values day=%d, month=%d, year=%d" % (day,month,year)) 
5249                     dateHandler 
= wx
.DateTimeFromDMY(day
,month
,year
) 
5253 ##                    dbg('cannot convert string to valid date') 
5259                 # wxDateTime doesn't take kindly to leading/trailing spaces when parsing, 
5260                 # so we eliminate them here: 
5261                 timeStr     
= text
[self
._dateExtent
+1:].strip()         ## time portion of the string 
5263 ##                    dbg('timeStr: "%s"' % timeStr) 
5265                         checkTime    
= dateHandler
.ParseTime(timeStr
) 
5266                         valid 
= checkTime 
== len(timeStr
) 
5270 ##                        dbg('cannot convert string to valid time') 
5272 ##        if valid: dbg('valid date') 
5277     def _validateTime(self
, candidate
=None): 
5278         """ Validate the current time value using the provided Regex filter. 
5279             Generally used for character types.BufferType 
5281 ##        dbg('MaskedEditMixin::_validateTime', indent=1) 
5282         # wxDateTime doesn't take kindly to leading/trailing spaces when parsing, 
5283         # so we eliminate them here: 
5284         if candidate 
is None: value 
= self
._GetValue
().strip() 
5285         else: value 
= candidate
.strip() 
5286 ##        dbg('value = "%s"' % value) 
5287         valid 
= True   # assume True until proven otherwise 
5289         dateHandler 
= wx
.DateTime_Today() 
5291             checkTime    
= dateHandler
.ParseTime(value
) 
5292 ##            dbg('checkTime:', checkTime, 'len(value)', len(value)) 
5293             valid 
= checkTime 
== len(value
) 
5298 ##            dbg('cannot convert string to valid time') 
5300 ##        if valid: dbg('valid time') 
5305     def _OnKillFocus(self
,event
): 
5306         """ Handler for EVT_KILL_FOCUS event. 
5308 ##        dbg('MaskedEditMixin::_OnKillFocus', 'isDate=',self._isDate, indent=1) 
5309         if self
.IsBeingDeleted() or self
.GetParent().IsBeingDeleted(): 
5311         if self
._mask 
and self
._IsEditable
(): 
5312             self
._AdjustField
(self
._GetInsertionPoint
()) 
5313             self
._CheckValid
()   ## Call valid handler 
5315         self
._LostFocus
()    ## Provided for subclass use 
5320     def _fixSelection(self
): 
5322         This gets called after the TAB traversal selection is made, if the 
5323         focus event was due to this, but before the EVT_LEFT_* events if 
5324         the focus shift was due to a mouse event. 
5326         The trouble is that, a priori, there's no explicit notification of 
5327         why the focus event we received.  However, the whole reason we need to 
5328         do this is because the default behavior on TAB traveral in a wx.TextCtrl is 
5329         now to select the entire contents of the window, something we don't want. 
5330         So we can *now* test the selection range, and if it's "the whole text" 
5331         we can assume the cause, change the insertion point to the start of 
5332         the control, and deselect. 
5334 ##        dbg('MaskedEditMixin::_fixSelection', indent=1) 
5335         # can get here if called with wx.CallAfter after underlying  
5336         # control has been destroyed on close, but after focus 
5338         if not self 
or not self
._mask 
or not self
._IsEditable
(): 
5342         sel_start
, sel_to 
= self
._GetSelection
() 
5343 ##        dbg('sel_start, sel_to:', sel_start, sel_to, 'self.IsEmpty()?', self.IsEmpty()) 
5345         if( sel_start 
== 0 and sel_to 
>= len( self
._mask 
)   #(can be greater in numeric controls because of reserved space) 
5346             and (not self
._ctrl
_constraints
._autoSelect 
or self
.IsEmpty() or self
.IsDefault() ) ): 
5347             # This isn't normally allowed, and so assume we got here by the new 
5348             # "tab traversal" behavior, so we need to reset the selection 
5349             # and insertion point: 
5350 ##            dbg('entire text selected; resetting selection to start of control') 
5352             field 
= self
._FindField
(self
._GetInsertionPoint
()) 
5353             edit_start
, edit_end 
= field
._extent
 
5354             if field
._selectOnFieldEntry
: 
5355                 self
._SetInsertionPoint
(edit_start
) 
5356                 self
._SetSelection
(edit_start
, edit_end
) 
5358             elif field
._insertRight
: 
5359                 self
._SetInsertionPoint
(edit_end
) 
5360                 self
._SetSelection
(edit_end
, edit_end
) 
5362         elif (self
._isFloat 
or self
._isInt
): 
5364             text
, signpos
, right_signpos 
= self
._getAbsValue
() 
5365             if text 
is None or text 
== self
._template
: 
5366                 integer 
= self
._fields
[0] 
5367                 edit_start
, edit_end 
= integer
._extent
 
5369                 if integer
._selectOnFieldEntry
: 
5370 ##                    dbg('select on field entry:') 
5371                     self
._SetInsertionPoint
(edit_start
) 
5372                     self
._SetSelection
(edit_start
, edit_end
) 
5374                 elif integer
._insertRight
: 
5375 ##                    dbg('moving insertion point to end') 
5376                     self
._SetInsertionPoint
(edit_end
) 
5377                     self
._SetSelection
(edit_end
, edit_end
) 
5379 ##                    dbg('numeric ctrl is empty; start at beginning after sign') 
5380                     self
._SetInsertionPoint
(signpos
+1)   ## Move past minus sign space if signed 
5381                     self
._SetSelection
(signpos
+1, signpos
+1) 
5383         elif sel_start 
> self
._goEnd
(getPosOnly
=True): 
5384 ##            dbg('cursor beyond the end of the user input; go to end of it') 
5387 ##            dbg('sel_start, sel_to:', sel_start, sel_to, 'self._masklength:', self._masklength) 
5392     def _Keypress(self
,key
): 
5393         """ Method provided to override OnChar routine. Return False to force 
5394             a skip of the 'normal' OnChar process. Called before class OnChar. 
5399     def _LostFocus(self
): 
5400         """ Method provided for subclasses. _LostFocus() is called after 
5401             the class processes its EVT_KILL_FOCUS event code. 
5406     def _OnDoubleClick(self
, event
): 
5407         """ selects field under cursor on dclick.""" 
5408         pos 
= self
._GetInsertionPoint
() 
5409         field 
= self
._FindField
(pos
) 
5410         start
, end 
= field
._extent
 
5411         self
._SetInsertionPoint
(start
) 
5412         self
._SetSelection
(start
, end
) 
5416         """ Method provided for subclasses. Called by internal EVT_TEXT 
5417             handler. Return False to override the class handler, True otherwise. 
5424         Used to override the default Cut() method in base controls, instead 
5425         copying the selection to the clipboard and then blanking the selection, 
5426         leaving only the mask in the selected area behind. 
5427         Note: _Cut (read "undercut" ;-) must be called from a Cut() override in the 
5428         derived control because the mixin functions can't override a method of 
5431 ##        dbg("MaskedEditMixin::_Cut", indent=1) 
5432         value 
= self
._GetValue
() 
5433 ##        dbg('current value: "%s"' % value) 
5434         sel_start
, sel_to 
= self
._GetSelection
()                   ## check for a range of selected text 
5435 ##        dbg('selected text: "%s"' % value[sel_start:sel_to].strip()) 
5436         do 
= wx
.TextDataObject() 
5437         do
.SetText(value
[sel_start
:sel_to
].strip()) 
5438         wx
.TheClipboard
.Open() 
5439         wx
.TheClipboard
.SetData(do
) 
5440         wx
.TheClipboard
.Close() 
5442         if sel_to 
- sel_start 
!= 0: 
5447 # WS Note: overriding Copy is no longer necessary given that you 
5448 # can no longer select beyond the last non-empty char in the control. 
5450 ##    def _Copy( self ): 
5452 ##        Override the wx.TextCtrl's .Copy function, with our own 
5453 ##        that does validation.  Need to strip trailing spaces. 
5455 ##        sel_start, sel_to = self._GetSelection() 
5456 ##        select_len = sel_to - sel_start 
5457 ##        textval = wx.TextCtrl._GetValue(self) 
5459 ##        do = wx.TextDataObject() 
5460 ##        do.SetText(textval[sel_start:sel_to].strip()) 
5461 ##        wx.TheClipboard.Open() 
5462 ##        wx.TheClipboard.SetData(do) 
5463 ##        wx.TheClipboard.Close() 
5466     def _getClipboardContents( self 
): 
5467         """ Subroutine for getting the current contents of the clipboard. 
5469         do 
= wx
.TextDataObject() 
5470         wx
.TheClipboard
.Open() 
5471         success 
= wx
.TheClipboard
.GetData(do
) 
5472         wx
.TheClipboard
.Close() 
5477             # Remove leading and trailing spaces before evaluating contents 
5478             return do
.GetText().strip() 
5481     def _validatePaste(self
, paste_text
, sel_start
, sel_to
, raise_on_invalid
=False): 
5483         Used by paste routine and field choice validation to see 
5484         if a given slice of paste text is legal for the area in question: 
5485         returns validity, replacement text, and extent of paste in 
5489 ##        dbg('MaskedEditMixin::_validatePaste("%(paste_text)s", %(sel_start)d, %(sel_to)d), raise_on_invalid? %(raise_on_invalid)d' % locals(), indent=1) 
5490         select_length 
= sel_to 
- sel_start
 
5491         maxlength 
= select_length
 
5492 ##        dbg('sel_to - sel_start:', maxlength) 
5494             maxlength 
= self
._masklength 
- sel_start
 
5498 ##        dbg('maxlength:', maxlength) 
5499         if 'unicode' in wx
.PlatformInfo 
and type(paste_text
) != types
.UnicodeType
: 
5500             paste_text 
= paste_text
.decode(self
._defaultEncoding
) 
5502         length_considered 
= len(paste_text
) 
5503         if length_considered 
> maxlength
: 
5504 ##            dbg('paste text will not fit into the %s:' % item, indent=0) 
5505             if raise_on_invalid
: 
5506 ##                dbg(indent=0, suspend=0) 
5507                 if item 
== 'control': 
5508                     raise ValueError('"%s" will not fit into the control "%s"' % (paste_text
, self
.name
)) 
5510                     raise ValueError('"%s" will not fit into the selection' % paste_text
) 
5512 ##                dbg(indent=0, suspend=0) 
5513                 return False, None, None 
5515         text 
= self
._template
 
5516 ##        dbg('length_considered:', length_considered) 
5519         replacement_text 
= "" 
5520         replace_to 
= sel_start
 
5522         while valid_paste 
and i 
< length_considered 
and replace_to 
< self
._masklength
: 
5523             if paste_text
[i
:] == self
._template
[replace_to
:length_considered
]: 
5524                 # remainder of paste matches template; skip char-by-char analysis 
5525 ##                dbg('remainder paste_text[%d:] (%s) matches template[%d:%d]' % (i, paste_text[i:], replace_to, length_considered)) 
5526                 replacement_text 
+= paste_text
[i
:] 
5527                 replace_to 
= i 
= length_considered
 
5530             char 
= paste_text
[i
] 
5531             field 
= self
._FindField
(replace_to
) 
5532             if not field
._compareNoCase
: 
5533                 if field
._forceupper
:   char 
= char
.upper() 
5534                 elif field
._forcelower
: char 
= char
.lower() 
5536 ##            dbg('char:', "'"+char+"'", 'i =', i, 'replace_to =', replace_to) 
5537 ##            dbg('self._isTemplateChar(%d)?' % replace_to, self._isTemplateChar(replace_to)) 
5538             if not self
._isTemplateChar
(replace_to
) and self
._isCharAllowed
( char
, replace_to
, allowAutoSelect
=False, ignoreInsertRight
=True): 
5539                 replacement_text 
+= char
 
5540 ##                dbg("not template(%(replace_to)d) and charAllowed('%(char)s',%(replace_to)d)" % locals()) 
5541 ##                dbg("replacement_text:", '"'+replacement_text+'"') 
5544             elif( char 
== self
._template
[replace_to
] 
5545                   or (self
._signOk 
and 
5546                           ( (i 
== 0 and (char 
== '-' or (self
._useParens 
and char 
== '('))) 
5547                             or (i 
== self
._masklength 
- 1 and self
._useParens 
and char 
== ')') ) ) ): 
5548                 replacement_text 
+= char
 
5549 ##                dbg("'%(char)s' == template(%(replace_to)d)" % locals()) 
5550 ##                dbg("replacement_text:", '"'+replacement_text+'"') 
5554                 next_entry 
= self
._findNextEntry
(replace_to
, adjustInsert
=False) 
5555                 if next_entry 
== replace_to
: 
5558                     replacement_text 
+= self
._template
[replace_to
:next_entry
] 
5559 ##                    dbg("skipping template; next_entry =", next_entry) 
5560 ##                    dbg("replacement_text:", '"'+replacement_text+'"') 
5561                     replace_to 
= next_entry  
# so next_entry will be considered on next loop 
5563         if not valid_paste 
and raise_on_invalid
: 
5564 ##            dbg('raising exception', indent=0, suspend=0) 
5565             raise ValueError('"%s" cannot be inserted into the control "%s"' % (paste_text
, self
.name
)) 
5567         elif i 
< len(paste_text
): 
5569             if raise_on_invalid
: 
5570 ##                dbg('raising exception', indent=0, suspend=0) 
5571                 raise ValueError('"%s" will not fit into the control "%s"' % (paste_text
, self
.name
)) 
5573 ##        dbg('valid_paste?', valid_paste) 
5575 ##            dbg('replacement_text: "%s"' % replacement_text, 'replace to:', replace_to) 
5577 ##        dbg(indent=0, suspend=0) 
5578         return valid_paste
, replacement_text
, replace_to
 
5581     def _Paste( self
, value
=None, raise_on_invalid
=False, just_return_value
=False ): 
5583         Used to override the base control's .Paste() function, 
5584         with our own that does validation. 
5585         Note: _Paste must be called from a Paste() override in the 
5586         derived control because the mixin functions can't override a 
5587         method of a sibling class. 
5589 ##        dbg('MaskedEditMixin::_Paste (value = "%s")' % value, indent=1) 
5591             paste_text 
= self
._getClipboardContents
() 
5595         if paste_text 
is not None: 
5597             if 'unicode' in wx
.PlatformInfo 
and type(paste_text
) != types
.UnicodeType
: 
5598                 paste_text 
= paste_text
.decode(self
._defaultEncoding
) 
5600 ##            dbg('paste text: "%s"' % paste_text) 
5601             # (conversion will raise ValueError if paste isn't legal) 
5602             sel_start
, sel_to 
= self
._GetSelection
() 
5603 ##            dbg('selection:', (sel_start, sel_to)) 
5605             # special case: handle allowInsert fields properly 
5606             field 
= self
._FindField
(sel_start
) 
5607             edit_start
, edit_end 
= field
._extent
 
5609             if field
._allowInsert 
and sel_to 
<= edit_end 
and (sel_start 
+ len(paste_text
) < edit_end 
or field
._insertRight
): 
5610                 if field
._insertRight
: 
5611                     # want to paste to the left; see if it will fit: 
5612                     left_text 
= self
._GetValue
()[edit_start
:sel_start
].lstrip() 
5613 ##                    dbg('len(left_text):', len(left_text)) 
5614 ##                    dbg('len(paste_text):', len(paste_text)) 
5615 ##                    dbg('sel_start - (len(left_text) + len(paste_text)) >= edit_start?', sel_start - (len(left_text) + len(paste_text)) >= edit_start) 
5616                     if sel_start 
- (len(left_text
) - (sel_to 
- sel_start
) + len(paste_text
)) >= edit_start
: 
5617                         # will fit! create effective paste text, and move cursor back to do so: 
5618                         paste_text 
= left_text 
+ paste_text
 
5619                         sel_start 
-= len(left_text
) 
5620                         paste_text 
= paste_text
.rjust(sel_to 
- sel_start
) 
5621 ##                        dbg('modified paste_text to be: "%s"' % paste_text) 
5622 ##                        dbg('modified selection to:', (sel_start, sel_to)) 
5624 ##                        dbg("won't fit left;", 'paste text remains: "%s"' % paste_text) 
5627                     paste_text 
= paste_text 
+ self
._GetValue
()[sel_to
:edit_end
].rstrip() 
5628 ##                    dbg("allow insert, but not insert right;", 'paste text set to: "%s"' % paste_text) 
5631                 new_pos 
= sel_start 
+ len(paste_text
)   # store for subsequent positioning 
5632 ##                dbg('paste within insertable field; adjusted paste_text: "%s"' % paste_text, 'end:', edit_end) 
5633 ##                dbg('expanded selection to:', (sel_start, sel_to)) 
5635             # Another special case: paste won't fit, but it's a right-insert field where entire 
5636             # non-empty value is selected, and there's room if the selection is expanded leftward: 
5637             if( len(paste_text
) > sel_to 
- sel_start
 
5638                 and field
._insertRight
 
5639                 and sel_start 
> edit_start
 
5640                 and sel_to 
>= edit_end
 
5641                 and not self
._GetValue
()[edit_start
:sel_start
].strip() ): 
5642                 # text won't fit within selection, but left of selection is empty; 
5643                 # check to see if we can expand selection to accommodate the value: 
5644                 empty_space 
= sel_start 
- edit_start
 
5645                 amount_needed 
= len(paste_text
) - (sel_to 
- sel_start
) 
5646                 if amount_needed 
<= empty_space
: 
5647                     sel_start 
-= amount_needed
 
5648 ##                    dbg('expanded selection to:', (sel_start, sel_to)) 
5651             # another special case: deal with signed values properly: 
5653                 signedvalue
, signpos
, right_signpos 
= self
._getSignedValue
() 
5654                 paste_signpos 
= paste_text
.find('-') 
5655                 if paste_signpos 
== -1: 
5656                     paste_signpos 
= paste_text
.find('(') 
5658                 # if paste text will result in signed value: 
5659 ####                dbg('paste_signpos != -1?', paste_signpos != -1) 
5660 ####                dbg('sel_start:', sel_start, 'signpos:', signpos) 
5661 ####                dbg('field._insertRight?', field._insertRight) 
5662 ####                dbg('sel_start - len(paste_text) >= signpos?', sel_start - len(paste_text) <= signpos) 
5663                 if paste_signpos 
!= -1 and (sel_start 
<= signpos
 
5664                                             or (field
._insertRight 
and sel_start 
- len(paste_text
) <= signpos
)): 
5668                 # remove "sign" from paste text, so we can auto-adjust for sign type after paste: 
5669                 paste_text 
= paste_text
.replace('-', ' ').replace('(',' ').replace(')','') 
5670 ##                dbg('unsigned paste text: "%s"' % paste_text) 
5674             # another special case: deal with insert-right fields when selection is empty and 
5675             # cursor is at end of field: 
5676 ####            dbg('field._insertRight?', field._insertRight) 
5677 ####            dbg('sel_start == edit_end?', sel_start == edit_end) 
5678 ####            dbg('sel_start', sel_start, 'sel_to', sel_to) 
5679             if field
._insertRight 
and sel_start 
== edit_end 
and sel_start 
== sel_to
: 
5680                 sel_start 
-= len(paste_text
) 
5683 ##                dbg('adjusted selection:', (sel_start, sel_to)) 
5686                 valid_paste
, replacement_text
, replace_to 
= self
._validatePaste
(paste_text
, sel_start
, sel_to
, raise_on_invalid
) 
5688 ##                dbg('exception thrown', indent=0) 
5692 ##                dbg('paste text not legal for the selection or portion of the control following the cursor;') 
5693                 if not wx
.Validator_IsSilent(): 
5698             text 
= self
._eraseSelection
() 
5700             new_text 
= text
[:sel_start
] + replacement_text 
+ text
[replace_to
:] 
5702                 new_text 
= string
.ljust(new_text
,self
._masklength
) 
5704                 new_text
, signpos
, right_signpos 
= self
._getSignedValue
(candidate
=new_text
) 
5707                         new_text 
= new_text
[:signpos
] + '(' + new_text
[signpos
+1:right_signpos
] + ')' + new_text
[right_signpos
+1:] 
5709                         new_text 
= new_text
[:signpos
] + '-' + new_text
[signpos
+1:] 
5713 ##            dbg("new_text:", '"'+new_text+'"') 
5715             if not just_return_value
: 
5716                 if new_text 
!= self
._GetValue
(): 
5717                     self
.modified 
= True 
5721                     wx
.CallAfter(self
._SetValue
, new_text
) 
5723                         new_pos 
= sel_start 
+ len(replacement_text
) 
5724                     wx
.CallAfter(self
._SetInsertionPoint
, new_pos
) 
5727                 return new_text
, replace_to
 
5728         elif just_return_value
: 
5730             return self
._GetValue
(), sel_to
 
5733     def _Undo(self
, value
=None, prev
=None, just_return_results
=False): 
5734         """ Provides an Undo() method in base controls. """ 
5735 ##        dbg("MaskedEditMixin::_Undo", indent=1) 
5737             value 
= self
._GetValue
() 
5739             prev 
= self
._prevValue
 
5740 ##        dbg('current value:  "%s"' % value) 
5741 ##        dbg('previous value: "%s"' % prev) 
5743 ##            dbg('no previous value', indent=0) 
5747             # Determine what to select: (relies on fixed-length strings) 
5748             # (This is a lot harder than it would first appear, because 
5749             # of mask chars that stay fixed, and so break up the "diff"...) 
5751             # Determine where they start to differ: 
5753             length 
= len(value
)     # (both are same length in masked control) 
5755             while( value
[:i
] == prev
[:i
] ): 
5760             # handle signed values carefully, so undo from signed to unsigned or vice-versa 
5763                 text
, signpos
, right_signpos 
= self
._getSignedValue
(candidate
=prev
) 
5765                     if prev
[signpos
] == '(' and prev
[right_signpos
] == ')': 
5769                     # eliminate source of "far-end" undo difference if using balanced parens: 
5770                     value 
= value
.replace(')', ' ') 
5771                     prev 
= prev
.replace(')', ' ') 
5772                 elif prev
[signpos
] == '-': 
5777             # Determine where they stop differing in "undo" result: 
5778             sm 
= difflib
.SequenceMatcher(None, a
=value
, b
=prev
) 
5779             i
, j
, k 
= sm
.find_longest_match(sel_start
, length
, sel_start
, length
) 
5780 ##            dbg('i,j,k = ', (i,j,k), 'value[i:i+k] = "%s"' % value[i:i+k], 'prev[j:j+k] = "%s"' % prev[j:j+k] ) 
5782             if k 
== 0:                              # no match found; select to end 
5785                 code_5tuples 
= sm
.get_opcodes() 
5786                 for op
, i1
, i2
, j1
, j2 
in code_5tuples
: 
5787 ##                    dbg("%7s value[%d:%d] (%s) prev[%d:%d] (%s)" % (op, i1, i2, value[i1:i2], j1, j2, prev[j1:j2])) 
5791                 # look backward through operations needed to produce "previous" value; 
5792                 # first change wins: 
5793                 for next_op 
in range(len(code_5tuples
)-1, -1, -1): 
5794                     op
, i1
, i2
, j1
, j2 
= code_5tuples
[next_op
] 
5795 ##                    dbg('value[i1:i2]: "%s"' % value[i1:i2], 'template[i1:i2] "%s"' % self._template[i1:i2]) 
5796                     field 
= self
._FindField
(i2
) 
5797                     if op 
== 'insert' and prev
[j1
:j2
] != self
._template
[j1
:j2
]: 
5798 ##                        dbg('insert found: selection =>', (j1, j2)) 
5803                     elif op 
== 'delete' and value
[i1
:i2
] != self
._template
[i1
:i2
]: 
5804                         edit_start
, edit_end 
= field
._extent
 
5805                         if field
._insertRight 
and (field
._allowInsert 
or i2 
== edit_end
): 
5811 ##                        dbg('delete found: selection =>', (sel_start, sel_to)) 
5814                     elif op 
== 'replace': 
5815                         if not prev
[i1
:i2
].strip() and field
._insertRight
: 
5816                             sel_start 
= sel_to 
= j2
 
5820 ##                        dbg('replace found: selection =>', (sel_start, sel_to)) 
5826                     # now go forwards, looking for earlier changes: 
5827 ##                    dbg('searching forward...') 
5828                     for next_op 
in range(len(code_5tuples
)): 
5829                         op
, i1
, i2
, j1
, j2 
= code_5tuples
[next_op
] 
5830                         field 
= self
._FindField
(i1
) 
5833                         elif op 
== 'replace': 
5834                             if field
._insertRight
: 
5835                                 # if replace with spaces in an insert-right control, ignore "forward" replace 
5836                                 if not prev
[i1
:i2
].strip(): 
5839 ##                                    dbg('setting sel_start to', j1) 
5842 ##                                    dbg('setting sel_start to', i1) 
5845 ##                                dbg('setting sel_start to', i1) 
5847 ##                            dbg('saw replace; breaking') 
5849                         elif op 
== 'insert' and not value
[i1
:i2
]: 
5850 ##                            dbg('forward %s found' % op) 
5851                             if prev
[j1
:j2
].strip(): 
5852 ##                                dbg('item to insert non-empty; setting sel_start to', j1) 
5855                             elif not field
._insertRight
: 
5856 ##                                dbg('setting sel_start to inserted space:', j1) 
5859                         elif op 
== 'delete': 
5860 ##                            dbg('delete; field._insertRight?', field._insertRight, 'value[%d:%d].lstrip: "%s"' % (i1,i2,value[i1:i2].lstrip())) 
5861                             if field
._insertRight
: 
5862                                 if value
[i1
:i2
].lstrip(): 
5863 ##                                    dbg('setting sel_start to ', j1) 
5865 ##                                    dbg('breaking loop') 
5870 ##                                dbg('saw delete; breaking') 
5873 ##                            dbg('unknown code!') 
5874                             # we've got what we need 
5879 ##                    dbg('no insert,delete or replace found (!)') 
5880                     # do "left-insert"-centric processing of difference based on l.c.s.: 
5881                     if i 
== j 
and j 
!= sel_start
:         # match starts after start of selection 
5882                         sel_to 
= sel_start 
+ (j
-sel_start
)  # select to start of match 
5884                         sel_to 
= j                          
# (change ends at j) 
5887             # There are several situations where the calculated difference is 
5888             # not what we want to select.  If changing sign, or just adding 
5889             # group characters, we really don't want to highlight the characters 
5890             # changed, but instead leave the cursor where it is. 
5891             # Also, there a situations in which the difference can be ambiguous; 
5894             # current value:    11234 
5895             # previous value:   1111234 
5897             # Where did the cursor actually lie and which 1s were selected on the delete 
5900             # Also, difflib can "get it wrong;" Consider: 
5902             # current value:    "       128.66" 
5903             # previous value:   "       121.86" 
5905             # difflib produces the following opcodes, which are sub-optimal: 
5906             #    equal value[0:9] (       12) prev[0:9] (       12) 
5907             #   insert value[9:9] () prev[9:11] (1.) 
5908             #    equal value[9:10] (8) prev[11:12] (8) 
5909             #   delete value[10:11] (.) prev[12:12] () 
5910             #    equal value[11:12] (6) prev[12:13] (6) 
5911             #   delete value[12:13] (6) prev[13:13] () 
5913             # This should have been: 
5914             #    equal value[0:9] (       12) prev[0:9] (       12) 
5915             #  replace value[9:11] (8.6) prev[9:11] (1.8) 
5916             #    equal value[12:13] (6) prev[12:13] (6) 
5918             # But it didn't figure this out! 
5920             # To get all this right, we use the previous selection recorded to help us... 
5922             if (sel_start
, sel_to
) != self
._prevSelection
: 
5923 ##                dbg('calculated selection', (sel_start, sel_to), "doesn't match previous", self._prevSelection) 
5925                 prev_sel_start
, prev_sel_to 
= self
._prevSelection
 
5926                 field 
= self
._FindField
(sel_start
) 
5928                       and sel_start 
< self
._masklength
 
5929                       and (prev
[sel_start
] in ('-', '(', ')') 
5930                                      or value
[sel_start
] in ('-', '(', ')')) ): 
5931                     # change of sign; leave cursor alone... 
5932 ##                    dbg("prev[sel_start] in ('-', '(', ')')?", prev[sel_start] in ('-', '(', ')')) 
5933 ##                    dbg("value[sel_start] in ('-', '(', ')')?", value[sel_start] in ('-', '(', ')')) 
5934 ##                    dbg('setting selection to previous one') 
5935                     sel_start
, sel_to 
= self
._prevSelection
 
5937                 elif field
._groupdigits 
and (value
[sel_start
:sel_to
] == field
._groupChar
 
5938                                              or prev
[sel_start
:sel_to
] == field
._groupChar
): 
5939                     # do not highlight grouping changes 
5940 ##                    dbg('value[sel_start:sel_to] == field._groupChar?', value[sel_start:sel_to] == field._groupChar) 
5941 ##                    dbg('prev[sel_start:sel_to] == field._groupChar?', prev[sel_start:sel_to] == field._groupChar) 
5942 ##                    dbg('setting selection to previous one') 
5943                     sel_start
, sel_to 
= self
._prevSelection
 
5946                     calc_select_len 
= sel_to 
- sel_start
 
5947                     prev_select_len 
= prev_sel_to 
- prev_sel_start
 
5949 ##                    dbg('sel_start == prev_sel_start', sel_start == prev_sel_start) 
5950 ##                    dbg('sel_to > prev_sel_to', sel_to > prev_sel_to) 
5952                     if prev_select_len 
>= calc_select_len
: 
5953                         # old selection was bigger; trust it: 
5954 ##                        dbg('prev_select_len >= calc_select_len?', prev_select_len >= calc_select_len) 
5955                         if not field
._insertRight
: 
5956 ##                            dbg('setting selection to previous one') 
5957                             sel_start
, sel_to 
= self
._prevSelection
 
5959                             sel_to 
= self
._prevSelection
[1] 
5960 ##                            dbg('setting selection to', (sel_start, sel_to)) 
5962                     elif( sel_to 
> prev_sel_to                  
# calculated select past last selection 
5963                           and prev_sel_to 
< len(self
._template
) # and prev_sel_to not at end of control 
5964                           and sel_to 
== len(self
._template
) ):  # and calculated selection goes to end of control 
5966                         i
, j
, k 
= sm
.find_longest_match(prev_sel_to
, length
, prev_sel_to
, length
) 
5967 ##                        dbg('i,j,k = ', (i,j,k), 'value[i:i+k] = "%s"' % value[i:i+k], 'prev[j:j+k] = "%s"' % prev[j:j+k] ) 
5969                             # difflib must not have optimized opcodes properly; 
5973                         # look for possible ambiguous diff: 
5975                         # if last change resulted in no selection, test from resulting cursor position: 
5976                         if prev_sel_start 
== prev_sel_to
: 
5977                             calc_select_len 
= sel_to 
- sel_start
 
5978                             field 
= self
._FindField
(prev_sel_start
) 
5980                             # determine which way to search from last cursor position for ambiguous change: 
5981                             if field
._insertRight
: 
5982                                 test_sel_start 
= prev_sel_start
 
5983                                 test_sel_to 
= prev_sel_start 
+ calc_select_len
 
5985                                 test_sel_start 
= prev_sel_start 
- calc_select_len
 
5986                                 test_sel_to 
= prev_sel_start
 
5988                             test_sel_start
, test_sel_to 
= prev_sel_start
, prev_sel_to
 
5990 ##                        dbg('test selection:', (test_sel_start, test_sel_to)) 
5991 ##                        dbg('calc change: "%s"' % prev[sel_start:sel_to]) 
5992 ##                        dbg('test change: "%s"' % prev[test_sel_start:test_sel_to]) 
5994                         # if calculated selection spans characters, and same characters 
5995                         # "before" the previous insertion point are present there as well, 
5996                         # select the ones related to the last known selection instead. 
5997                         if( sel_start 
!= sel_to
 
5998                             and test_sel_to 
< len(self
._template
) 
5999                             and prev
[test_sel_start
:test_sel_to
] == prev
[sel_start
:sel_to
] ): 
6001                             sel_start
, sel_to 
= test_sel_start
, test_sel_to
 
6003                 # finally, make sure that the old and new values are 
6004                 # different where we say they're different: 
6005                 while( sel_to 
- 1 > 0 
6006                         and sel_to 
> sel_start
 
6007                         and value
[sel_to
-1:] == prev
[sel_to
-1:]): 
6009                 while( sel_start 
+ 1 < self
._masklength
 
6010                         and sel_start 
< sel_to
 
6011                         and value
[:sel_start
+1] == prev
[:sel_start
+1]): 
6014 ##            dbg('sel_start, sel_to:', sel_start, sel_to) 
6015 ##            dbg('previous value: "%s"' % prev) 
6017             if just_return_results
: 
6018                 return prev
, (sel_start
, sel_to
) 
6020             self
._SetValue
(prev
) 
6021             self
._SetInsertionPoint
(sel_start
) 
6022             self
._SetSelection
(sel_start
, sel_to
) 
6025 ##            dbg('no difference between previous value') 
6027             if just_return_results
: 
6028                 return prev
, self
._GetSelection
() 
6031     def _OnClear(self
, event
): 
6032         """ Provides an action for context menu delete operation """ 
6036     def _OnContextMenu(self
, event
): 
6037 ##        dbg('MaskedEditMixin::OnContextMenu()', indent=1) 
6039         menu
.Append(wx
.ID_UNDO
, "Undo", "") 
6040         menu
.AppendSeparator() 
6041         menu
.Append(wx
.ID_CUT
, "Cut", "") 
6042         menu
.Append(wx
.ID_COPY
, "Copy", "") 
6043         menu
.Append(wx
.ID_PASTE
, "Paste", "") 
6044         menu
.Append(wx
.ID_CLEAR
, "Delete", "") 
6045         menu
.AppendSeparator() 
6046         menu
.Append(wx
.ID_SELECTALL
, "Select All", "") 
6048         wx
.EVT_MENU(menu
, wx
.ID_UNDO
, self
._OnCtrl
_Z
) 
6049         wx
.EVT_MENU(menu
, wx
.ID_CUT
, self
._OnCtrl
_X
) 
6050         wx
.EVT_MENU(menu
, wx
.ID_COPY
, self
._OnCtrl
_C
) 
6051         wx
.EVT_MENU(menu
, wx
.ID_PASTE
, self
._OnCtrl
_V
) 
6052         wx
.EVT_MENU(menu
, wx
.ID_CLEAR
, self
._OnClear
) 
6053         wx
.EVT_MENU(menu
, wx
.ID_SELECTALL
, self
._OnCtrl
_A
) 
6055         # ## WSS: The base control apparently handles 
6056         # enable/disable of wx.ID_CUT, wx.ID_COPY, wx.ID_PASTE 
6057         # and wx.ID_CLEAR menu items even if the menu is one 
6058         # we created.  However, it doesn't do undo properly, 
6059         # so we're keeping track of previous values ourselves. 
6060         # Therefore, we have to override the default update for 
6061         # that item on the menu: 
6062         wx
.EVT_UPDATE_UI(self
, wx
.ID_UNDO
, self
._UndoUpdateUI
) 
6063         self
._contextMenu 
= menu
 
6065         self
.PopupMenu(menu
, event
.GetPosition()) 
6067         self
._contextMenu 
= None 
6070     def _UndoUpdateUI(self
, event
): 
6071         if self
._prevValue 
is None or self
._prevValue 
== self
._curValue
: 
6072             self
._contextMenu
.Enable(wx
.ID_UNDO
, False) 
6074             self
._contextMenu
.Enable(wx
.ID_UNDO
, True) 
6077     def _OnCtrlParametersChanged(self
): 
6079         Overridable function to allow derived classes to take action as a 
6080         result of parameter changes prior to possibly changing the value 
6085  ## ---------- ---------- ---------- ---------- ---------- ---------- ---------- 
6086 class MaskedEditAccessorsMixin
: 
6088     To avoid a ton of boiler-plate, and to automate the getter/setter generation 
6089     for each valid control parameter so we never forget to add the functions when 
6090     adding parameters, this class programmatically adds the masked edit mixin 
6091     parameters to itself. 
6092     (This makes it easier for Designers like Boa to deal with masked controls.) 
6094     To further complicate matters, this is done with an extra level of inheritance, 
6095     so that "general" classes like masked.TextCtrl can have all possible attributes, 
6096     while derived classes, like masked.TimeCtrl and masked.NumCtrl can prevent 
6097     exposure of those optional attributes of their base class that do not make 
6098     sense for their derivation. 
6100     Therefore, we define: 
6101         BaseMaskedTextCtrl(TextCtrl, MaskedEditMixin) 
6103         masked.TextCtrl(BaseMaskedTextCtrl, MaskedEditAccessorsMixin). 
6105     This allows us to then derive: 
6106         masked.NumCtrl( BaseMaskedTextCtrl ) 
6108     and not have to expose all the same accessor functions for the 
6109     derived control when they don't all make sense for it. 
6113     # Define the default set of attributes exposed by the most generic masked controls: 
6114     exposed_basectrl_params 
= MaskedEditMixin
.valid_ctrl_params
.keys() + Field
.valid_params
.keys() 
6115     exposed_basectrl_params
.remove('index') 
6116     exposed_basectrl_params
.remove('extent') 
6117     exposed_basectrl_params
.remove('foregroundColour')   # (base class already has this) 
6119     for param 
in exposed_basectrl_params
: 
6120         propname 
= param
[0].upper() + param
[1:] 
6121         exec('def Set%s(self, value): self.SetCtrlParameters(%s=value)' % (propname
, param
)) 
6122         exec('def Get%s(self): return self.GetCtrlParameter("%s")''' % (propname, param)) 
6124         if param.find('Colour
') != -1: 
6125             # add non-british spellings, for backward-compatibility 
6126             propname.replace('Colour
', 'Color
') 
6128             exec('def Set
%s(self
, value
): self
.SetCtrlParameters(%s=value
)' % (propname, param)) 
6129             exec('def Get
%s(self
): return self
.GetCtrlParameter("%s")''' % (propname, param)) 
6134 ## ---------- ---------- ---------- ---------- ---------- ---------- ---------- 
6135 ## these are helper subroutines: 
6137 def _movetofloat( origvalue, fmtstring, neg, addseparators=False, sepchar = ',',fillchar=' '): 
6138     """ addseparators = add separator character every three numerals if True 
6140     fmt0 = fmtstring.split('.') 
6143     val  = origvalue.split('.')[0].strip() 
6144     ret  = fillchar * (len(fmt1)-len(val)) + val + "." + "0" * len(fmt2) 
6147     return (ret,len(fmt1)) 
6150 def _isDateType( fmtstring ): 
6151     """ Checks the mask and returns True if it fits an allowed 
6152         date or datetime format. 
6154     dateMasks = ("^##/##/####", 
6166     reString  = "|".join(dateMasks) 
6167     filter = re.compile( reString) 
6168     if re.match(filter,fmtstring): return True 
6171 def _isTimeType( fmtstring ): 
6172     """ Checks the mask and returns True if it fits an allowed 
6175     reTimeMask = "^##:##(:##)?( (AM|PM))?" 
6176     filter = re.compile( reTimeMask ) 
6177     if re.match(filter,fmtstring): return True 
6181 def _isFloatingPoint( fmtstring): 
6182     filter = re.compile("[ ]?[#]+\.[#]+\n") 
6183     if re.match(filter,fmtstring+"\n"): return True 
6187 def _isInteger( fmtstring ): 
6188     filter = re.compile("[#]+\n") 
6189     if re.match(filter,fmtstring+"\n"): return True 
6193 def _getDateParts( dateStr, dateFmt ): 
6194     if len(dateStr) > 11: clip = dateStr[0:11] 
6195     else:                 clip = dateStr 
6196     if clip[-2] not in string.digits: 
6197         clip = clip[:-1]    # (got part of time; drop it) 
6199     dateSep = (('/' in clip) * '/') + (('-' in clip) * '-') + (('.' in clip) * '.') 
6200     slices  = clip.split(dateSep) 
6201     if dateFmt == "MDY": 
6202         y,m,d = (slices[2],slices[0],slices[1])  ## year, month, date parts 
6203     elif dateFmt == "DMY": 
6204         y,m,d = (slices[2],slices[1],slices[0])  ## year, month, date parts 
6205     elif dateFmt == "YMD": 
6206         y,m,d = (slices[0],slices[1],slices[2])  ## year, month, date parts 
6208         y,m,d = None, None, None 
6215 def _getDateSepChar(dateStr): 
6216     clip   = dateStr[0:10] 
6217     dateSep = (('/' in clip) * '/') + (('-' in clip) * '-') + (('.' in clip) * '.') 
6221 def _makeDate( year, month, day, dateFmt, dateStr): 
6222     sep    = _getDateSepChar( dateStr) 
6223     if dateFmt == "MDY": 
6224         return "%s%s%s%s%s" % (month,sep,day,sep,year)  ## year, month, date parts 
6225     elif dateFmt == "DMY": 
6226         return "%s%s%s%s%s" % (day,sep,month,sep,year)  ## year, month, date parts 
6227     elif dateFmt == "YMD": 
6228         return "%s%s%s%s%s" % (year,sep,month,sep,day)  ## year, month, date parts 
6233 def _getYear(dateStr,dateFmt): 
6234     parts = _getDateParts( dateStr, dateFmt) 
6237 def _getMonth(dateStr,dateFmt): 
6238     parts = _getDateParts( dateStr, dateFmt) 
6241 def _getDay(dateStr,dateFmt): 
6242     parts = _getDateParts( dateStr, dateFmt) 
6245 ## ---------- ---------- ---------- ---------- ---------- ---------- ---------- 
6246 class __test(wx.PySimpleApp): 
6248             from wx.lib.rcsizer import RowColSizer 
6249             self.frame = wx.Frame( None, -1, "MaskedEditMixin 0.0.7 Demo Page #1", size = (700,600)) 
6250             self.panel = wx.Panel( self.frame, -1) 
6251             self.sizer = RowColSizer() 
6256             id, id1 = wx.NewId(), wx.NewId() 
6257             self.command1  = wx.Button( self.panel, id, "&Close" ) 
6258             self.command2  = wx.Button( self.panel, id1, "&AutoFormats" ) 
6259             self.sizer.Add(self.command1, row=0, col=0, flag=wx.ALL, border = 5) 
6260             self.sizer.Add(self.command2, row=0, col=1, colspan=2, flag=wx.ALL, border = 5) 
6261             self.panel.Bind(wx.EVT_BUTTON, self.onClick, self.command1 ) 
6262 ##            self.panel.SetDefaultItem(self.command1 ) 
6263             self.panel.Bind(wx.EVT_BUTTON, self.onClickPage, self.command2) 
6265             self.check1 = wx.CheckBox( self.panel, -1, "Disallow Empty" ) 
6266             self.check2 = wx.CheckBox( self.panel, -1, "Highlight Empty" ) 
6267             self.sizer.Add( self.check1, row=0,col=3, flag=wx.ALL,border=5 ) 
6268             self.sizer.Add( self.check2, row=0,col=4, flag=wx.ALL,border=5 ) 
6269             self.panel.Bind(wx.EVT_CHECKBOX, self._onCheck1, self.check1 ) 
6270             self.panel.Bind(wx.EVT_CHECKBOX, self._onCheck2, self.check2 ) 
6273             label = """Press ctrl-s in any field to output the value and plain value. Press ctrl-x to clear and re-set any field. 
6274 Note that all controls have been auto-sized by including F in the format code. 
6275 Try entering nonsensical or partial values in validated fields to see what happens (use ctrl-s to test the valid status).""" 
6276             label2 = "\nNote that the State and Last Name fields are list-limited (Name:Smith,Jones,Williams)." 
6278             self.label1 = wx.StaticText( self.panel, -1, label) 
6279             self.label2 = wx.StaticText( self.panel, -1, "Description") 
6280             self.label3 = wx.StaticText( self.panel, -1, "Mask Value") 
6281             self.label4 = wx.StaticText( self.panel, -1, "Format") 
6282             self.label5 = wx.StaticText( self.panel, -1, "Reg Expr Val. (opt)") 
6283             self.label6 = wx.StaticText( self.panel, -1, "MaskedEdit Ctrl") 
6284             self.label7 = wx.StaticText( self.panel, -1, label2) 
6285             self.label7.SetForegroundColour("Blue") 
6286             self.label1.SetForegroundColour("Blue") 
6287             self.label2.SetFont(wx.Font(9,wx.SWISS,wx.NORMAL,wx.BOLD)) 
6288             self.label3.SetFont(wx.Font(9,wx.SWISS,wx.NORMAL,wx.BOLD)) 
6289             self.label4.SetFont(wx.Font(9,wx.SWISS,wx.NORMAL,wx.BOLD)) 
6290             self.label5.SetFont(wx.Font(9,wx.SWISS,wx.NORMAL,wx.BOLD)) 
6291             self.label6.SetFont(wx.Font(9,wx.SWISS,wx.NORMAL,wx.BOLD)) 
6293             self.sizer.Add( self.label1, row=1,col=0,colspan=7, flag=wx.ALL,border=5) 
6294             self.sizer.Add( self.label7, row=2,col=0,colspan=7, flag=wx.ALL,border=5) 
6295             self.sizer.Add( self.label2, row=3,col=0, flag=wx.ALL,border=5) 
6296             self.sizer.Add( self.label3, row=3,col=1, flag=wx.ALL,border=5) 
6297             self.sizer.Add( self.label4, row=3,col=2, flag=wx.ALL,border=5) 
6298             self.sizer.Add( self.label5, row=3,col=3, flag=wx.ALL,border=5) 
6299             self.sizer.Add( self.label6, row=3,col=4, flag=wx.ALL,border=5) 
6301             # The following list is of the controls for the demo. Feel free to play around with 
6304             #description        mask                    excl format     regexp                              range,list,initial 
6305            ("Phone No",         "(###) ###-#### x:###", "", 'F!^-R',    "^\(\d\d\d\) \d\d\d-\d\d\d\d",    (),[],''), 
6306            ("Last Name Only",   "C{14}",                "", 'F {list}', '^[A-Z][a-zA-Z]+',                  (),('Smith','Jones','Williams'),''), 
6307            ("Full Name",        "C{14}",                "", 'F_',       '^[A-Z][a-zA-Z]+ [A-Z][a-zA-Z]+',   (),[],''), 
6308            ("Social Sec#",      "###-##-####",          "", 'F',        "\d{3}-\d{2}-\d{4}",                (),[],''), 
6309            ("U.S. Zip+4",       "#{5}-#{4}",            "", 'F',        "\d{5}-(\s{4}|\d{4})",(),[],''), 
6310            ("U.S. State (2 char)\n(with default)","AA",                 "", 'F!',       "[A-Z]{2}",                         (),states, 'AZ'), 
6311            ("Customer No",      "\CAA-###",              "", 'F!',      "C[A-Z]{2}-\d{3}",                   (),[],''), 
6312            ("Date (MDY) + Time\n(with default)",      "##/##/#### ##:## AM",  'BCDEFGHIJKLMNOQRSTUVWXYZ','DFR!',"",                (),[], r'03/05/2003 12:00 AM'), 
6313            ("Invoice Total",    "#{9}.##",              "", 'F-R,',     "",                                 (),[], ''), 
6314            ("Integer (signed)\n(with default)", "#{6}",                 "", 'F-R',      "",                                 (),[], '0     '), 
6315            ("Integer (unsigned)\n(with default), 1-399", "######",      "", 'F',        "",                                 (1,399),[], '1     '), 
6316            ("Month selector",   "XXX",                  "", 'F',        "",                                 (), 
6317                 ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'],""), 
6318            ("fraction selector","#/##",                 "", 'F',        "^\d\/\d\d?",                       (), 
6319                 ['2/3', '3/4', '1/2', '1/4', '1/8', '1/16', '1/32', '1/64'], "") 
6322             for control in controls: 
6323                 self.sizer.Add( wx.StaticText( self.panel, -1, control[0]),row=rowcount, col=0,border=5,flag=wx.ALL) 
6324                 self.sizer.Add( wx.StaticText( self.panel, -1, control[1]),row=rowcount, col=1,border=5, flag=wx.ALL) 
6325                 self.sizer.Add( wx.StaticText( self.panel, -1, control[3]),row=rowcount, col=2,border=5, flag=wx.ALL) 
6326                 self.sizer.Add( wx.StaticText( self.panel, -1, control[4][:20]),row=rowcount, col=3,border=5, flag=wx.ALL) 
6328                 if control in controls[:]:#-2]: 
6329                     newControl  = MaskedTextCtrl( self.panel, -1, "", 
6331                                                     excludeChars = control[2], 
6332                                                     formatcodes  = control[3], 
6334                                                     validRegex   = control[4], 
6335                                                     validRange   = control[5], 
6336                                                     choices      = control[6], 
6337                                                     defaultValue = control[7], 
6339                     if control[6]: newControl.SetCtrlParameters(choiceRequired = True) 
6341                     newControl = MaskedComboBox(  self.panel, -1, "", 
6342                                                     choices = control[7], 
6343                                                     choiceRequired  = True, 
6345                                                     formatcodes  = control[3], 
6346                                                     excludeChars = control[2], 
6348                                                     validRegex   = control[4], 
6349                                                     validRange   = control[5], 
6351                 self.editList.append( newControl ) 
6353                 self.sizer.Add( newControl, row=rowcount,col=4,flag=wx.ALL,border=5) 
6356             self.sizer.AddGrowableCol(4) 
6358             self.panel.SetSizer(self.sizer) 
6359             self.panel.SetAutoLayout(1) 
6366         def onClick(self, event): 
6369         def onClickPage(self, event): 
6370             self.page2 = __test2(self.frame,-1,"") 
6371             self.page2.Show(True) 
6373         def _onCheck1(self,event): 
6374             """ Set required value on/off """ 
6375             value = event.IsChecked() 
6377                 for control in self.editList: 
6378                     control.SetCtrlParameters(emptyInvalid=True) 
6381                 for control in self.editList: 
6382                     control.SetCtrlParameters(emptyInvalid=False) 
6384             self.panel.Refresh() 
6386         def _onCheck2(self,event): 
6387             """ Highlight empty values""" 
6388             value = event.IsChecked() 
6390                 for control in self.editList: 
6391                     control.SetCtrlParameters( emptyBackgroundColour = 'Aquamarine') 
6394                 for control in self.editList: 
6395                     control.SetCtrlParameters( emptyBackgroundColour = 'White') 
6397             self.panel.Refresh() 
6400 ## ---------- ---------- ---------- ---------- ---------- ---------- ---------- 
6402 class __test2(wx.Frame): 
6403         def __init__(self, parent, id, caption): 
6404             wx.Frame.__init__( self, parent, id, "MaskedEdit control 0.0.7 Demo Page #2 -- AutoFormats", size = (550,600)) 
6405             from wx.lib.rcsizer import RowColSizer 
6406             self.panel = wx.Panel( self, -1) 
6407             self.sizer = RowColSizer() 
6413 All these controls have been created by passing a single parameter, the AutoFormat code. 
6414 The class contains an internal dictionary of types and formats (autoformats). 
6415 To see a great example of validations in action, try entering a bad email address, then tab out.""" 
6417             self.label1 = wx.StaticText( self.panel, -1, label) 
6418             self.label2 = wx.StaticText( self.panel, -1, "Description") 
6419             self.label3 = wx.StaticText( self.panel, -1, "AutoFormat Code") 
6420             self.label4 = wx.StaticText( self.panel, -1, "MaskedEdit Control") 
6421             self.label1.SetForegroundColour("Blue") 
6422             self.label2.SetFont(wx.Font(9,wx.SWISS,wx.NORMAL,wx.BOLD)) 
6423             self.label3.SetFont(wx.Font(9,wx.SWISS,wx.NORMAL,wx.BOLD)) 
6424             self.label4.SetFont(wx.Font(9,wx.SWISS,wx.NORMAL,wx.BOLD)) 
6426             self.sizer.Add( self.label1, row=1,col=0,colspan=3, flag=wx.ALL,border=5) 
6427             self.sizer.Add( self.label2, row=3,col=0, flag=wx.ALL,border=5) 
6428             self.sizer.Add( self.label3, row=3,col=1, flag=wx.ALL,border=5) 
6429             self.sizer.Add( self.label4, row=3,col=2, flag=wx.ALL,border=5) 
6431             id, id1 = wx.NewId(), wx.NewId() 
6432             self.command1  = wx.Button( self.panel, id, "&Close") 
6433             self.command2  = wx.Button( self.panel, id1, "&Print Formats") 
6434             self.panel.Bind(wx.EVT_BUTTON, self.onClick, self.command1) 
6435             self.panel.SetDefaultItem(self.command1) 
6436             self.panel.Bind(wx.EVT_BUTTON, self.onClickPrint, self.command2) 
6438             # The following list is of the controls for the demo. Feel free to play around with 
6441            ("Phone No","USPHONEFULLEXT"), 
6442            ("US Date + Time","USDATETIMEMMDDYYYY/HHMM"), 
6443            ("US Date MMDDYYYY","USDATEMMDDYYYY/"), 
6444            ("Time (with seconds)","TIMEHHMMSS"), 
6445            ("Military Time\n(without seconds)","24HRTIMEHHMM"), 
6446            ("Social Sec#","USSOCIALSEC"), 
6447            ("Credit Card","CREDITCARD"), 
6448            ("Expiration MM/YY","EXPDATEMMYY"), 
6449            ("Percentage","PERCENT"), 
6450            ("Person's Age","AGE"), 
6451            ("US Zip Code","USZIP"), 
6452            ("US Zip+4","USZIPPLUS4"), 
6453            ("Email Address","EMAIL"), 
6454            ("IP Address", "(derived control IpAddrCtrl)") 
6457             for control in controls: 
6458                 self.sizer.Add( wx.StaticText( self.panel, -1, control[0]),row=rowcount, col=0,border=5,flag=wx.ALL) 
6459                 self.sizer.Add( wx.StaticText( self.panel, -1, control[1]),row=rowcount, col=1,border=5, flag=wx.ALL) 
6460                 if control in controls[:-1]: 
6461                     self.sizer.Add( MaskedTextCtrl( self.panel, -1, "", 
6462                                                       autoformat  = control[1], 
6464                                 row=rowcount,col=2,flag=wx.ALL,border=5) 
6466                     self.sizer.Add( IpAddrCtrl( self.panel, -1, "", demo=True ), 
6467                                     row=rowcount,col=2,flag=wx.ALL,border=5) 
6470             self.sizer.Add(self.command1, row=0, col=0, flag=wx.ALL, border = 5) 
6471             self.sizer.Add(self.command2, row=0, col=1, flag=wx.ALL, border = 5) 
6472             self.sizer.AddGrowableCol(3) 
6474             self.panel.SetSizer(self.sizer) 
6475             self.panel.SetAutoLayout(1) 
6477         def onClick(self, event): 
6480         def onClickPrint(self, event): 
6481             for format in masktags.keys(): 
6482                 sep = "+------------------------+" 
6483                 print "%s\n%s  \n  Mask: %s \n  RE Validation string: %s\n" % (sep,format, masktags[format]['mask'], masktags[format]['validRegex']) 
6485 ## ---------- ---------- ---------- ---------- ---------- ---------- ---------- 
6487 if __name__ == "__main__": 
6493 ## =================================== 
6495 ## 1. WS: For some reason I don't understand, the control is generating two (2) 
6496 ##      EVT_TEXT events for every one (1) .SetValue() of the underlying control. 
6497 ##      I've been unsuccessful in determining why or in my efforts to make just one 
6498 ##      occur.  So, I've added a hack to save the last seen value from the 
6499 ##      control in the EVT_TEXT handler, and if *different*, call event.Skip() 
6500 ##      to propagate it down the event chain, and let the application see it. 
6502 ## 2. WS: MaskedComboBox is deficient in several areas, all having to do with the 
6503 ##      behavior of the underlying control that I can't fix.  The problems are: 
6504 ##      a) The background coloring doesn't work in the text field of the control; 
6505 ##         instead, there's a only border around it that assumes the correct color. 
6506 ##      b) The control will not pass WXK_TAB to the event handler, no matter what 
6507 ##         I do, and there's no style wxCB_PROCESS_TAB like wxTE_PROCESS_TAB to 
6508 ##         indicate that we want these events.  As a result, MaskedComboBox 
6509 ##         doesn't do the nice field-tabbing that MaskedTextCtrl does. 
6510 ##      c) Auto-complete had to be reimplemented for the control because programmatic 
6511 ##         setting of the value of the text field does not set up the auto complete 
6512 ##         the way that the control processing keystrokes does.  (But I think I've 
6513 ##         implemented a fairly decent approximation.)  Because of this the control 
6514 ##         also won't auto-complete on dropdown, and there's no event I can catch 
6515 ##         to work around this problem. 
6516 ##      d) There is no method provided for getting the selection; the hack I've 
6517 ##         implemented has its flaws, not the least of which is that due to the 
6518 ##         strategy that I'm using, the paste buffer is always replaced by the 
6519 ##         contents of the control's selection when in focus, on each keystroke; 
6520 ##         this makes it impossible to paste anything into a MaskedComboBox 
6521 ##         at the moment... :-( 
6522 ##      e) The other deficient behavior, likely induced by the workaround for (d), 
6523 ##         is that you can can't shift-left to select more than one character 
6527 ## 3. WS: Controls on wxPanels don't seem to pass Shift-WXK_TAB to their 
6528 ##      EVT_KEY_DOWN or EVT_CHAR event handlers.  Until this is fixed in 
6529 ##      wxWindows, shift-tab won't take you backwards through the fields of 
6530 ##      a MaskedTextCtrl like it should.  Until then Shifted arrow keys will 
6531 ##      work like shift-tab and tab ought to. 
6535 ## =============================## 
6536 ##  1. Add Popup list for auto-completable fields that simulates combobox on individual 
6537 ##     fields.  Example: City validates against list of cities, or zip vs zip code list. 
6538 ##  2. Allow optional monetary symbols (eg. $, pounds, etc.) at front of a "decimal" 
6540 ##  3. Fix shift-left selection for MaskedComboBox. 
6541 ##  5. Transform notion of "decimal control" to be less "entire control"-centric, 
6542 ##     so that monetary symbols can be included and still have the appropriate 
6543 ##     semantics.  (Big job, as currently written, but would make control even 
6544 ##     more useful for business applications.) 
6548 ## ==================== 
6550 ##  1. Fixed bug involving incorrect variable name, causing combobox autocomplete to fail. 
6551 ##  2. Added proper support for unicode version of wxPython 
6552 ##  3. Added * as mask char meaning "all ansi chars" (ordinals 32-255). 
6553 ##  4. Converted doc strings to use reST format, for ePyDoc documentation. 
6554 ##  5. Renamed helper functions, classes, etc. not intended to be visible in public 
6555 ##     interface to code. 
6558 ##  1. Fixed intra-right-insert-field erase, such that it doesn't leave a hole, but instead 
6559 ##     shifts the text to the left accordingly. 
6560 ##  2. Fixed _SetValue() to place cursor after last character inserted, rather than end of 
6562 ##  3. Fixed some incorrect undo behavior for right-insert fields, and allowed derived classes 
6563 ##     (eg. numctrl) to pass modified values for undo processing (to handle/ignore grouping 
6565 ##  4. Fixed autoselect behavior to work similarly to (2) above, so that combobox 
6566 ##     selection will only select the non-empty text, as per request. 
6567 ##  5. Fixed tabbing to work with 2.5.2 semantics. 
6568 ##  6. Fixed size calculation to handle changing fonts 
6571 ##  1. Reorganized masked controls into separate package, renamed things accordingly 
6572 ##  2. Split actual controls out of this file into their own files. 
6574 ##  (Reported) bugs fixed: 
6575 ##   1. Crash ensues if you attempt to change the mask of a read-only 
6576 ##      MaskedComboBox after initial construction. 
6577 ##   2. Changed strategy of defining Get/Set property functions so that 
6578 ##      these are now generated dynamically at runtime, rather than as 
6579 ##      part of the class definition.  (This makes it possible to have 
6580 ##      more general base classes that have many more options for configuration 
6581 ##      without requiring that derivations support the same options.) 
6582 ##   3. Fixed IsModified for _Paste() and _OnErase(). 
6585 ##   1. Fixed "attribute function inheritance," since base control is more 
6586 ##      generic than subsequent derivations, not all property functions of a 
6587 ##      generic control should be exposed in those derivations.  New strategy 
6588 ##      uses base control classes (eg. BaseMaskedTextCtrl) that should be 
6589 ##      used to derive new class types, and mixed with their own mixins to 
6590 ##      only expose those attributes from the generic masked controls that 
6591 ##      make sense for the derivation.  (This makes Boa happier.) 
6592 ##   2. Renamed (with b-c) MILTIME autoformats to 24HRTIME, so as to be less 
6596 ##  (Reported) bugs fixed: 
6597 ##   1. Right-click menu allowed "cut" operation that destroyed mask 
6598 ##      (was implemented by base control) 
6599 ##   2. MaskedComboBox didn't allow .Append() of mixed-case values; all 
6600 ##      got converted to lower case. 
6601 ##   3. MaskedComboBox selection didn't deal with spaces in values 
6602 ##      properly when autocompleting, and didn't have a concept of "next" 
6603 ##      match for handling choice list duplicates. 
6604 ##   4. Size of MaskedComboBox was always default. 
6605 ##   5. Email address regexp allowed some "non-standard" things, and wasn't 
6607 ##   6. Couldn't easily reset MaskedComboBox contents programmatically. 
6608 ##   7. Couldn't set emptyInvalid during construction. 
6609 ##   8. Under some versions of wxPython, readonly comboboxes can apparently 
6610 ##      return a GetInsertionPoint() result (655535), causing masked control 
6612 ##   9. Specifying an empty mask caused the controls to traceback. 
6613 ##  10. Can't specify float ranges for validRange. 
6614 ##  11. '.' from within a the static portion of a restricted IP address 
6615 ##      destroyed the mask from that point rightward; tab when cursor is 
6616 ##      before 1st field takes cursor past that field. 
6619 ##  12. Added Ctrl-Z/Undo handling, (and implemented context-menu properly.) 
6620 ##  13. Added auto-select option on char input for masked controls with 
6622 ##  14. Added '>' formatcode, allowing insert within a given or each field 
6623 ##      as appropriate, rather than requiring "overwrite".  This makes single 
6624 ##      field controls that just have validation rules (eg. EMAIL) much more 
6625 ##      friendly.  The same flag controls left shift when deleting vs just 
6626 ##      blanking the value, and for right-insert fields, allows right-insert 
6627 ##      at any non-blank (non-sign) position in the field. 
6628 ##  15. Added option to use to indicate negative values for numeric controls. 
6629 ##  16. Improved OnFocus handling of numeric controls. 
6630 ##  17. Enhanced Home/End processing to allow operation on a field level, 
6632 ##  18. Added individual Get/Set functions for control parameters, for 
6633 ##      simplified integration with Boa Constructor. 
6634 ##  19. Standardized "Colour" parameter names to match wxPython, with 
6635 ##      non-british spellings still supported for backward-compatibility. 
6636 ##  20. Added '&' mask specification character for punctuation only (no letters 
6638 ##  21. Added (in a separate file) wx.MaskedCtrl() factory function to provide 
6639 ##      unified interface to the masked edit subclasses. 
6643 ##   1. Made it possible to configure grouping, decimal and shift-decimal characters, 
6644 ##      to make controls more usable internationally. 
6645 ##   2. Added code to smart "adjust" value strings presented to .SetValue() 
6646 ##      for right-aligned numeric format controls if they are shorter than 
6647 ##      than the control width,  prepending the missing portion, prepending control 
6648 ##      template left substring for the missing characters, so that setting 
6649 ##      numeric values is easier. 
6650 ##   3. Renamed SetMaskParameters SetCtrlParameters() (with old name preserved 
6651 ##      for b-c), as this makes more sense. 
6654 ##   1. Fixed .SetValue() to replace the current value, rather than the current 
6655 ##      selection. Also changed it to generate ValueError if presented with 
6656 ##      either a value which doesn't follow the format or won't fit.  Also made 
6657 ##      set value adjust numeric and date controls as if user entered the value. 
6658 ##      Expanded doc explaining how SetValue() works. 
6659 ##   2. Fixed EUDATE* autoformats, fixed IsDateType mask list, and added ability to 
6660 ##      use 3-char months for dates, and EUDATETIME, and EUDATEMILTIME autoformats. 
6661 ##   3. Made all date autoformats automatically pick implied "datestyle". 
6662 ##   4. Added IsModified override, since base wx.TextCtrl never reports modified if 
6663 ##      .SetValue used to change the value, which is what the masked edit controls 
6665 ##   5. Fixed bug in date position adjustment on 2 to 4 digit date conversion when 
6666 ##      using tab to "leave field" and auto-adjust. 
6667 ##   6. Fixed bug in _isCharAllowed() for negative number insertion on pastes, 
6668 ##      and bug in ._Paste() that didn't account for signs in signed masks either. 
6669 ##   7. Fixed issues with _adjustPos for right-insert fields causing improper 
6670 ##      selection/replacement of values 
6671 ##   8. Fixed _OnHome handler to properly handle extending current selection to 
6672 ##      beginning of control. 
6673 ##   9. Exposed all (valid) autoformats to demo, binding descriptions to 
6675 ##  10. Fixed a couple of bugs in email regexp. 
6676 ##  11. Made maskchardict an instance var, to make mask chars to be more 
6677 ##      amenable to international use. 
6678 ##  12. Clarified meaning of '-' formatcode in doc. 
6679 ##  13. Fixed a couple of coding bugs being flagged by Python2.1. 
6680 ##  14. Fixed several issues with sign positioning, erasure and validity 
6681 ##      checking for "numeric" masked controls. 
6682 ##  15. Added validation to IpAddrCtrl.SetValue(). 
6685 ##   1. Changed calling interface to use boolean "useFixedWidthFont" (True by default) 
6686 ##      vs. literal font facename, and use wxTELETYPE as the font family 
6688 ##   2. Switched to use of dbg module vs. locally defined version. 
6689 ##   3. Revamped entire control structure to use Field classes to hold constraint 
6690 ##      and formatting data, to make code more hierarchical, allow for more 
6691 ##      sophisticated masked edit construction. 
6692 ##   4. Better strategy for managing options, and better validation on keywords. 
6693 ##   5. Added 'V' format code, which requires that in order for a character 
6694 ##      to be accepted, it must result in a string that passes the validRegex. 
6695 ##   6. Added 'S' format code which means "select entire field when navigating 
6697 ##   7. Added 'r' format code to allow "right-insert" fields. (implies 'R'--right-alignment) 
6698 ##   8. Added '<' format code to allow fields to require explicit cursor movement 
6700 ##   9. Added validFunc option to other validation mechanisms, that allows derived 
6701 ##      classes to add dynamic validation constraints to the control. 
6702 ##  10. Fixed bug in validatePaste code causing possible IndexErrors, and also 
6703 ##      fixed failure to obey case conversion codes when pasting. 
6704 ##  11. Implemented '0' (zero-pad) formatting code, as it wasn't being done anywhere... 
6705 ##  12. Removed condition from OnDecimalPoint, so that it always truncates right on '.' 
6706 ##  13. Enhanced IpAddrCtrl to use right-insert fields, selection on field traversal, 
6707 ##      individual field validation to prevent field values > 255, and require explicit 
6708 ##      tab/. to change fields. 
6709 ##  14. Added handler for left double-click to select field under cursor. 
6710 ##  15. Fixed handling for "Read-only" styles. 
6711 ##  16. Separated signedForegroundColor from 'R' style, and added foregroundColor 
6712 ##      attribute, for more consistent and controllable coloring. 
6713 ##  17. Added retainFieldValidation parameter, allowing top-level constraints 
6714 ##      such as "validRequired" to be set independently of field-level equivalent. 
6715 ##      (needed in TimeCtrl for bounds constraints.) 
6716 ##  18. Refactored code a bit, cleaned up and commented code more heavily, fixed 
6717 ##      some of the logic for setting/resetting parameters, eg. fillChar, defaultValue, 
6719 ##  19. Fixed maskchar setting for upper/lowercase, to work in all locales. 
6723 ##   1. Decimal point behavior restored for decimal and integer type controls: 
6724 ##      decimal point now trucates the portion > 0. 
6725 ##   2. Return key now works like the tab character and moves to the next field, 
6726 ##      provided no default button is set for the form panel on which the control 
6728 ##   3. Support added in _FindField() for subclasses controls (like timecontrol) 
6729 ##      to determine where the current insertion point is within the mask (i.e. 
6730 ##      which sub-'field'). See method documentation for more info and examples. 
6731 ##   4. Added Field class and support for all constraints to be field-specific 
6732 ##      in addition to being globally settable for the control. 
6733 ##      Choices for each field are validated for length and pastability into 
6734 ##      the field in question, raising ValueError if not appropriate for the control. 
6735 ##      Also added selective additional validation based on individual field constraints. 
6736 ##      By default, SHIFT-WXK_DOWN, SHIFT-WXK_UP, WXK_PRIOR and WXK_NEXT all 
6737 ##      auto-complete fields with choice lists, supplying the 1st entry in 
6738 ##      the choice list if the field is empty, and cycling through the list in 
6739 ##      the appropriate direction if already a match.  WXK_DOWN will also auto- 
6740 ##      complete if the field is partially completed and a match can be made. 
6741 ##      SHIFT-WXK_UP/DOWN will also take you to the next field after any 
6742 ##      auto-completion performed. 
6743 ##   5. Added autoCompleteKeycodes=[] parameters for allowing further 
6744 ##      customization of the control.  Any keycode supplied as a member 
6745 ##      of the _autoCompleteKeycodes list will be treated like WXK_NEXT.  If 
6746 ##      requireFieldChoice is set, then a valid value from each non-empty 
6747 ##      choice list will be required for the value of the control to validate. 
6748 ##   6. Fixed "auto-sizing" to be relative to the font actually used, rather 
6749 ##      than making assumptions about character width. 
6750 ##   7. Fixed GetMaskParameter(), which was non-functional in previous version. 
6751 ##   8. Fixed exceptions raised to provide info on which control had the error. 
6752 ##   9. Fixed bug in choice management of MaskedComboBox. 
6753 ##  10. Fixed bug in IpAddrCtrl causing traceback if field value was of 
6754 ##     the form '# #'.  Modified control code for IpAddrCtrl so that '.' 
6755 ##     in the middle of a field clips the rest of that field, similar to 
6756 ##     decimal and integer controls. 
6760 ##   1. "-" is a toggle for sign; "+" now changes - signed numerics to positive. 
6761 ##   2. ',' in formatcodes now causes numeric values to be comma-delimited (e.g.333,333). 
6762 ##   3. New support for selecting text within the control.(thanks Will Sadkin!) 
6763 ##      Shift-End and Shift-Home now select text as you would expect 
6764 ##      Control-Shift-End selects to the end of the mask string, even if value not entered. 
6765 ##      Control-A selects all *entered* text, Shift-Control-A selects everything in the control. 
6766 ##   4. event.Skip() added to onKillFocus to correct remnants when running in Linux (contributed- 
6767 ##      for some reason I couldn't find the original email but thanks!!!) 
6768 ##   5. All major key-handling code moved to their own methods for easier subclassing: OnHome, 
6769 ##      OnErase, OnEnd, OnCtrl_X, OnCtrl_A, etc. 
6770 ##   6. Email and autoformat validations corrected using regex provided by Will Sadkin (thanks!). 
6771 ##   (The rest of the changes in this version were done by Will Sadkin with permission from Jeff...) 
6772 ##   7. New mechanism for replacing default behavior for any given key, using 
6773 ##      ._SetKeycodeHandler(keycode, func) and ._SetKeyHandler(char, func) now available 
6774 ##      for easier subclassing of the control. 
6775 ##   8. Reworked the delete logic, cut, paste and select/replace logic, as well as some bugs 
6776 ##      with insertion point/selection modification.  Changed Ctrl-X to use standard "cut" 
6777 ##      semantics, erasing the selection, rather than erasing the entire control. 
6778 ##   9. Added option for an "default value" (ie. the template) for use when a single fillChar 
6779 ##      is not desired in every position.  Added IsDefault() function to mean "does the value 
6780 ##      equal the template?" and modified .IsEmpty() to mean "do all of the editable 
6781 ##      positions in the template == the fillChar?" 
6782 ##  10. Extracted mask logic into mixin, so we can have both MaskedTextCtrl and MaskedComboBox, 
6784 ##  11. MaskedComboBox now adds the capability to validate from list of valid values. 
6785 ##      Example: City validates against list of cities, or zip vs zip code list. 
6786 ##  12. Fixed oversight in EVT_TEXT handler that prevented the events from being 
6787 ##      passed to the next handler in the event chain, causing updates to the 
6788 ##      control to be invisible to the parent code. 
6789 ##  13. Added IPADDR autoformat code, and subclass IpAddrCtrl for controlling tabbing within 
6790 ##      the control, that auto-reformats as you move between cells. 
6791 ##  14. Mask characters [A,a,X,#] can now appear in the format string as literals, by using '\'. 
6792 ##  15. It is now possible to specify repeating masks, e.g. #{3}-#{3}-#{14} 
6793 ##  16. Fixed major bugs in date validation, due to the fact that 
6794 ##      wxDateTime.ParseDate is too liberal, and will accept any form that 
6795 ##      makes any kind of sense, regardless of the datestyle you specified 
6796 ##      for the control.  Unfortunately, the strategy used to fix it only 
6797 ##      works for versions of wxPython post 2.3.3.1, as a C++ assert box 
6798 ##      seems to show up on an invalid date otherwise, instead of a catchable 
6800 ##  17. Enhanced date adjustment to automatically adjust heuristic based on 
6801 ##      current year, making last century/this century determination on 
6802 ##      2-digit year based on distance between today's year and value; 
6803 ##      if > 50 year separation, assume last century (and don't assume last 
6804 ##      century is 20th.) 
6805 ##  18. Added autoformats and support for including HHMMSS as well as HHMM for 
6806 ##      date times, and added similar time, and militaray time autoformats. 
6807 ##  19. Enhanced tabbing logic so that tab takes you to the next field if the 
6808 ##      control is a multi-field control. 
6809 ##  20. Added stub method called whenever the control "changes fields", that 
6810 ##      can be overridden by subclasses (eg. IpAddrCtrl.) 
6811 ##  21. Changed a lot of code to be more functionally-oriented so side-effects 
6812 ##      aren't as problematic when maintaining code and/or adding features. 
6813 ##      Eg: IsValid() now does not have side-effects; it merely reflects the 
6814 ##      validity of the value of the control; to determine validity AND recolor 
6815 ##      the control, _CheckValid() should be used with a value argument of None. 
6816 ##      Similarly, made most reformatting function take an optional candidate value 
6817 ##      rather than just using the current value of the control, and only 
6818 ##      have them change the value of the control if a candidate is not specified. 
6819 ##      In this way, you can do validation *before* changing the control. 
6820 ##  22. Changed validRequired to mean "disallow chars that result in invalid 
6821 ##      value."  (Old meaning now represented by emptyInvalid.)  (This was 
6822 ##      possible once I'd made the changes in (19) above.) 
6823 ##  23. Added .SetMaskParameters and .GetMaskParameter methods, so they 
6824 ##      can be set/modified/retrieved after construction.  Removed individual 
6825 ##      parameter setting functions, in favor of this mechanism, so that 
6826 ##      all adjustment of the control based on changing parameter values can 
6827 ##      be handled in one place with unified mechanism. 
6828 ##  24. Did a *lot* of testing and fixing re: numeric values.  Added ability 
6829 ##      to type "grouping char" (ie. ',') and validate as appropriate. 
6830 ##  25. Fixed ZIPPLUS4 to allow either 5 or 4, but if > 5 must be 9. 
6831 ##  26. Fixed assumption about "decimal or integer" masks so that they're only 
6832 ##      made iff there's no validRegex associated with the field.  (This 
6833 ##      is so things like zipcodes which look like integers can have more 
6834 ##      restrictive validation (ie. must be 5 digits.) 
6835 ##  27. Added a ton more doc strings to explain use and derivation requirements 
6836 ##      and did regularization of the naming conventions. 
6837 ##  28. Fixed a range bug in _adjustKey preventing z from being handled properly. 
6838 ##  29. Changed behavior of '.' (and shift-.) in numeric controls to move to 
6839 ##      reformat the value and move the next field as appropriate. (shift-'.', 
6840 ##      ie. '>' moves to the previous field. 
6843 ##   1. Fixed regex bug that caused autoformat AGE to invalidate any age ending 
6845 ##   2. New format character 'D' to trigger date type. If the user enters 2 digits in the 
6846 ##      year position, the control will expand the value to four digits, using numerals below 
6847 ##      50 as 21st century (20+nn) and less than 50 as 20th century (19+nn). 
6848 ##      Also, new optional parameter datestyle = set to one of {MDY|DMY|YDM} 
6849 ##   3. revalid parameter renamed validRegex to conform to standard for all validation 
6850 ##      parameters (see 2 new ones below). 
6851 ##   4. New optional init parameter = validRange. Used only for int/dec (numeric) types. 
6852 ##      Allows the developer to specify a valid low/high range of values. 
6853 ##   5. New optional init parameter = validList. Used for character types. Allows developer 
6854 ##      to send a list of values to the control to be used for specific validation. 
6855 ##      See the Last Name Only example - it is list restricted to Smith/Jones/Williams. 
6856 ##   6. Date type fields now use wxDateTime's parser to validate the date and time. 
6857 ##      This works MUCH better than my kludgy regex!! Thanks to Robin Dunn for pointing 
6858 ##      me toward this solution! 
6859 ##   7. Date fields now automatically expand 2-digit years when it can. For example, 
6860 ##      if the user types "03/10/67", then "67" will auto-expand to "1967". If a two-year 
6861 ##      date is entered it will be expanded in any case when the user tabs out of the 
6863 ##   8. New class functions: SetValidBackgroundColor, SetInvalidBackgroundColor, SetEmptyBackgroundColor, 
6864 ##      SetSignedForeColor allow accessto override default class coloring behavior. 
6865 ##   9. Documentation updated and improved. 
6866 ##  10. Demo - page 2 is now a wxFrame class instead of a wxPyApp class. Works better. 
6867 ##      Two new options (checkboxes) - test highlight empty and disallow empty. 
6868 ##  11. Home and End now work more intuitively, moving to the first and last user-entry 
6869 ##      value, respectively. 
6870 ##  12. New class function: SetRequired(bool). Sets the control's entry required flag 
6871 ##      (i.e. disallow empty values if True). 
6874 ##   1. get_plainValue method renamed to GetPlainValue following the wxWindows 
6875 ##      StudlyCaps(tm) standard (thanks Paul Moore).  ;) 
6876 ##   2. New format code 'F' causes the control to auto-fit (auto-size) itself 
6877 ##      based on the length of the mask template. 
6878 ##   3. Class now supports "autoformat" codes. These can be passed to the class 
6879 ##      on instantiation using the parameter autoformat="code". If the code is in 
6880 ##      the dictionary, it will self set the mask, formatting, and validation string. 
6881 ##      I have included a number of samples, but I am hoping that someone out there 
6882 ##      can help me to define a whole bunch more. 
6883 ##   4. I have added a second page to the demo (as well as a second demo class, test2) 
6884 ##      to showcase how autoformats work. The way they self-format and self-size is, 
6885 ##      I must say, pretty cool. 
6886 ##   5. Comments added and some internal cosmetic revisions re: matching the code 
6887 ##      standards for class submission. 
6888 ##   6. Regex validation is now done in real time - field turns yellow immediately 
6889 ##      and stays yellow until the entered value is valid 
6890 ##   7. Cursor now skips over template characters in a more intuitive way (before the 
6892 ##   8. Change, Keypress and LostFocus methods added for convenience of subclasses. 
6893 ##      Developer may use these methods which will be called after EVT_TEXT, EVT_CHAR, 
6894 ##      and EVT_KILL_FOCUS, respectively. 
6895 ##   9. Decimal and numeric handlers have been rewritten and now work more intuitively. 
6898 ##   1. New .IsEmpty() method returns True if the control's value is equal to the 
6899 ##      blank template string 
6900 ##   2. Control now supports a new init parameter: revalid. Pass a regular expression 
6901 ##      that the value will have to match when the control loses focus. If invalid, 
6902 ##      the control's BackgroundColor will turn yellow, and an internal flag is set (see next). 
6903 ##   3. Demo now shows revalid functionality. Try entering a partial value, such as a 
6904 ##      partial social security number. 
6905 ##   4. New .IsValid() value returns True if the control is empty, or if the value matches 
6906 ##      the revalid expression. If not, .IsValid() returns False. 
6907 ##   5. Decimal values now collapse to decimal with '.00' on losefocus if the user never 
6908 ##      presses the decimal point. 
6909 ##   6. Cursor now goes to the beginning of the field if the user clicks in an 
6910 ##      "empty" field intead of leaving the insertion point in the middle of the 
6912 ##   7. New "N" mask type includes upper and lower chars plus digits. a-zA-Z0-9. 
6913 ##   8. New formatcodes init parameter replaces other init params and adds functions. 
6914 ##      String passed to control on init controls: 
6918 ##        R Show negative #s in red 
6920 ##        - Signed numerals 
6921 ##        0 Numeric fields get leading zeros 
6922 ##   9. Ctrl-X in any field clears the current value. 
6923 ##   10. Code refactored and made more modular (esp in OnChar method). Should be more 
6924 ##       easy to read and understand. 
6925 ##   11. Demo enhanced. 
6926 ##   12. Now has _doc_. 
6929 ##   1. GetPlainValue() now returns the value without the template characters; 
6930 ##      so, for example, a social security number (123-33-1212) would return as 
6931 ##      123331212; also removes white spaces from numeric/decimal values, so 
6932 ##      "-   955.32" is returned "-955.32". Press ctrl-S to see the plain value. 
6933 ##   2. Press '.' in an integer style masked control and truncate any trailing digits. 
6934 ##   3. Code moderately refactored. Internal names improved for clarity. Additional 
6935 ##      internal documentation. 
6936 ##   4. Home and End keys now supported to move cursor to beginning or end of field. 
6937 ##   5. Un-signed integers and decimals now supported. 
6938 ##   6. Cosmetic improvements to the demo. 
6939 ##   7. Class renamed to MaskedTextCtrl. 
6940 ##   8. Can now specify include characters that will override the basic 
6941 ##      controls: for example, includeChars = "@." for email addresses 
6942 ##   9. Added mask character 'C' -> allow any upper or lowercase character 
6943 ##   10. .SetSignColor(str:color) sets the foreground color for negative values 
6944 ##       in signed controls (defaults to red) 
6945 ##   11. Overview documentation written. 
6948 ##   1. Tab now works properly when pressed in last position 
6949 ##   2. Decimal types now work (e.g. #####.##) 
6950 ##   3. Signed decimal or numeric values supported (i.e. negative numbers) 
6951 ##   4. Negative decimal or numeric values now can show in red. 
6952 ##   5. Can now specify an "exclude list" with the excludeChars parameter. 
6953 ##      See date/time formatted example - you can only enter A or P in the 
6954 ##      character mask space (i.e. AM/PM). 
6955 ##   6. Backspace now works properly, including clearing data from a selected 
6956 ##      region but leaving template characters intact. Also delete key. 
6957 ##   7. Left/right arrows now work properly. 
6958 ##   8. Removed EventManager call from test so demo should work with wxPython 2.3.3