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         (Setting a func of None removes any keyhandler for the given key.) 
 701         "Navigation" keys are assumed to change the cursor position, and 
 702         therefore don't cause automatic motion of the cursor as insertable 
 705         ._AddNavKeycode(keycode, handler=None) 
 706         ._AddNavKey(char, handler=None) 
 707                         Allows controls to specify other keys (and optional handlers) 
 708                         to be treated as navigational characters. (eg. '.' in IpAddrCtrl) 
 710         ._GetNavKeycodes()  Returns the current list of navigational keycodes. 
 712         ._SetNavKeycodes(key_func_tuples) 
 713                         Allows replacement of the current list of keycode 
 714                         processed as navigation keys, and bind associated 
 715                         optional keyhandlers. argument is a list of key/handler 
 716                         tuples.  Passing a value of None for the handler in a 
 717                         given tuple indicates that default processing for the key 
 720         ._FindField(pos) Returns the Field object associated with this position 
 723         ._FindFieldExtent(pos, getslice=False, value=None) 
 724                         Returns edit_start, edit_end of the field corresponding 
 725                         to the specified position within the control, and 
 726                         optionally also returns the current contents of that field. 
 727                         If value is specified, it will retrieve the slice the corresponding 
 728                         slice from that value, rather than the current value of the 
 732                         This is, the function that gets called for a given position 
 733                         whenever the cursor is adjusted to leave a given field. 
 734                         By default, it adjusts the year in date fields if mask is a date, 
 735                         It can be overridden by a derived class to 
 736                         adjust the value of the control at that time. 
 737                         (eg. IpAddrCtrl reformats the address in this way.) 
 739         ._Change()      Called by internal EVT_TEXT handler. Return False to force 
 740                         skip of the normal class change event. 
 741         ._Keypress(key) Called by internal EVT_CHAR handler. Return False to force 
 742                         skip of the normal class keypress event. 
 743         ._LostFocus()   Called by internal EVT_KILL_FOCUS handler 
 746                         This is the default EVT_KEY_DOWN routine; it just checks for 
 747                         "navigation keys", and if event.ControlDown(), it fires the 
 748                         mixin's _OnChar() routine, as such events are not always seen 
 749                         by the "cooked" EVT_CHAR routine. 
 751         ._OnChar(event) This is the main EVT_CHAR handler for the 
 754     The following routines are used to handle standard actions 
 756         _OnArrow(event)         used for arrow navigation events 
 757         _OnCtrl_A(event)        'select all' 
 758         _OnCtrl_C(event)        'copy' (uses base control function, as copy is non-destructive) 
 759         _OnCtrl_S(event)        'save' (does nothing) 
 760         _OnCtrl_V(event)        'paste' - calls _Paste() method, to do smart paste 
 761         _OnCtrl_X(event)        'cut'   - calls _Cut() method, to "erase" selection 
 762         _OnCtrl_Z(event)        'undo'  - resets value to previous value (if any) 
 764         _OnChangeField(event)   primarily used for tab events, but can be 
 765                                 used for other keys (eg. '.' in IpAddrCtrl) 
 767         _OnErase(event)         used for backspace and delete 
 771     The following routine provides a hook back to any class derivations, so that 
 772     they can react to parameter changes before any value is set/reset as a result of 
 773     those changes.  (eg. masked.ComboBox needs to detect when the choices list is 
 774     modified, either implicitly or explicitly, so it can reset the base control 
 775     to have the appropriate choice list *before* the initial value is reset to match.) 
 777         _OnCtrlParametersChanged() 
 781     For convenience, each class derived from MaskedEditMixin should 
 782     define an accessors mixin, so that it exposes only those parameters 
 783     that make sense for the derivation.  This is done with an intermediate 
 784     level of inheritance, ie: 
 786     class BaseMaskedTextCtrl( TextCtrl, MaskedEditMixin ): 
 788     class TextCtrl( BaseMaskedTextCtrl, MaskedEditAccessorsMixin ): 
 789     class ComboBox( BaseMaskedComboBox, MaskedEditAccessorsMixin ): 
 790     class NumCtrl( BaseMaskedTextCtrl, MaskedNumCtrlAccessorsMixin ): 
 791     class IpAddrCtrl( BaseMaskedTextCtrl, IpAddrCtrlAccessorsMixin ): 
 792     class TimeCtrl( BaseMaskedTextCtrl, TimeCtrlAccessorsMixin ): 
 796     Each accessors mixin defines Get/Set functions for the base class parameters 
 797     that are appropriate for that derivation. 
 798     This allows the base classes to be "more generic," exposing the widest 
 799     set of options, while not requiring derived classes to be so general. 
 810 # jmg 12/9/03 - when we cut ties with Py 2.2 and earlier, this would 
 811 # be a good place to implement the 2.3 logger class 
 812 from wx
.tools
.dbg 
import Logger
 
 817 ## ---------- ---------- ---------- ---------- ---------- ---------- ---------- 
 819 ## Constants for identifying control keys and classes of keys: 
 821 WXK_CTRL_A 
= (ord('A')+1) - ord('A')   ## These keys are not already defined in wx 
 822 WXK_CTRL_C 
= (ord('C')+1) - ord('A') 
 823 WXK_CTRL_S 
= (ord('S')+1) - ord('A') 
 824 WXK_CTRL_V 
= (ord('V')+1) - ord('A') 
 825 WXK_CTRL_X 
= (ord('X')+1) - ord('A') 
 826 WXK_CTRL_Z 
= (ord('Z')+1) - ord('A') 
 829     wx
.WXK_BACK
, wx
.WXK_LEFT
, wx
.WXK_RIGHT
, wx
.WXK_UP
, wx
.WXK_DOWN
, wx
.WXK_TAB
, 
 830     wx
.WXK_HOME
, wx
.WXK_END
, wx
.WXK_RETURN
, wx
.WXK_PRIOR
, wx
.WXK_NEXT
 
 834     wx
.WXK_BACK
, wx
.WXK_DELETE
, wx
.WXK_INSERT
, WXK_CTRL_A
, WXK_CTRL_C
, WXK_CTRL_S
, WXK_CTRL_V
, 
 835     WXK_CTRL_X
, WXK_CTRL_Z
 
 839 ## ---------- ---------- ---------- ---------- ---------- ---------- ---------- 
 841 ## Constants for masking. This is where mask characters 
 843 ##  maskchars used to identify valid mask characters from all others 
 844 ##   #- allow numeric 0-9 only 
 845 ##   A- allow uppercase only. Combine with forceupper to force lowercase to upper 
 846 ##   a- allow lowercase only. Combine with forcelower to force upper to lowercase 
 847 ##   X- allow any character (string.letters, string.punctuation, string.digits) 
 848 ## Note: locale settings affect what "uppercase", lowercase, etc comprise. 
 850 maskchars 
= ("#","A","a","X","C","N",'*','&') 
 852 for i 
in xrange(32, 256): 
 855 months 
= '(01|02|03|04|05|06|07|08|09|10|11|12)' 
 856 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)' 
 857 charmonths_dict 
= {'jan': 1, 'feb': 2, 'mar': 3, 'apr': 4, 'may': 5, 'jun': 6, 
 858                    'jul': 7, 'aug': 8, 'sep': 9, 'oct': 10, 'nov': 11, 'dec': 12} 
 860 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)' 
 861 hours  
= '(0\d| \d|1[012])' 
 862 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)' 
 863 minutes 
= """(00|01|02|03|04|05|06|07|08|09|10|11|12|13|14|15|\ 
 864 16|17|18|19|20|21|22|23|24|25|26|27|28|29|30|31|32|33|34|35|\ 
 865 36|37|38|39|40|41|42|43|44|45|46|47|48|49|50|51|52|53|54|55|\ 
 868 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' 
 870 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(',') 
 872 state_names 
= ['Alabama','Alaska','Arizona','Arkansas', 
 873                'California','Colorado','Connecticut', 
 874                'Delaware','District of Columbia', 
 875                'Florida','Georgia','Hawaii', 
 876                'Idaho','Illinois','Indiana','Iowa', 
 877                'Kansas','Kentucky','Louisiana', 
 878                'Maine','Maryland','Massachusetts','Michigan', 
 879                'Minnesota','Mississippi','Missouri','Montana', 
 880                'Nebraska','Nevada','New Hampshire','New Jersey', 
 881                'New Mexico','New York','North Carolina','North Dakokta', 
 882                'Ohio','Oklahoma','Oregon', 
 883                'Pennsylvania','Puerto Rico','Rhode Island', 
 884                'South Carolina','South Dakota', 
 885                'Tennessee','Texas','Utah', 
 886                'Vermont','Virginia', 
 887                'Washington','West Virginia', 
 888                'Wisconsin','Wyoming'] 
 890 ## ---------- ---------- ---------- ---------- ---------- ---------- ---------- 
 892 ## The following dictionary defines the current set of autoformats: 
 896            'mask': "(###) ###-#### x:###", 
 897            'formatcodes': 'F^->', 
 898            'validRegex': "^\(\d{3}\) \d{3}-\d{4}", 
 899            'description': "Phone Number w/opt. ext" 
 902            'mask': "###-###-#### x:###", 
 903            'formatcodes': 'F^->', 
 904            'validRegex': "^\d{3}-\d{3}-\d{4}", 
 905            'description': "Phone Number\n (w/hyphens and opt. ext)" 
 908            'mask': "(###) ###-####", 
 909            'formatcodes': 'F^->', 
 910            'validRegex': "^\(\d{3}\) \d{3}-\d{4}", 
 911            'description': "Phone Number only" 
 914            'mask': "###-###-####", 
 915            'formatcodes': 'F^->', 
 916            'validRegex': "^\d{3}-\d{3}-\d{4}", 
 917            'description': "Phone Number\n(w/hyphens)" 
 921            'formatcodes': 'F!V', 
 922            'validRegex': "([ACDFGHIKLMNOPRSTUVW] |%s)" % string
.join(states
,'|'), 
 924            'choiceRequired': True, 
 925            'description': "US State Code" 
 928            'mask': "ACCCCCCCCCCCCCCCCCCC", 
 930            'validRegex': "([ACDFGHIKLMNOPRSTUVW] |%s)" % string
.join(state_names
,'|'), 
 931            'choices': state_names
, 
 932            'choiceRequired': True, 
 933            'description': "US State Name" 
 936        "USDATETIMEMMDDYYYY/HHMMSS": { 
 937            'mask': "##/##/#### ##:##:## AM", 
 938            'excludeChars': am_pm_exclude
, 
 939            'formatcodes': 'DF!', 
 940            'validRegex': '^' + months 
+ '/' + days 
+ '/' + '\d{4} ' + hours 
+ ':' + minutes 
+ ':' + seconds 
+ ' (A|P)M', 
 941            'description': "US Date + Time" 
 943        "USDATETIMEMMDDYYYY-HHMMSS": { 
 944            'mask': "##-##-#### ##:##:## AM", 
 945            'excludeChars': am_pm_exclude
, 
 946            'formatcodes': 'DF!', 
 947            'validRegex': '^' + months 
+ '-' + days 
+ '-' + '\d{4} ' + hours 
+ ':' + minutes 
+ ':' + seconds 
+ ' (A|P)M', 
 948            'description': "US Date + Time\n(w/hypens)" 
 950        "USDATE24HRTIMEMMDDYYYY/HHMMSS": { 
 951            'mask': "##/##/#### ##:##:##", 
 953            'validRegex': '^' + months 
+ '/' + days 
+ '/' + '\d{4} ' + milhours 
+ ':' + minutes 
+ ':' + seconds
, 
 954            'description': "US Date + 24Hr (Military) Time" 
 956        "USDATE24HRTIMEMMDDYYYY-HHMMSS": { 
 957            'mask': "##-##-#### ##:##:##", 
 959            'validRegex': '^' + months 
+ '-' + days 
+ '-' + '\d{4} ' + milhours 
+ ':' + minutes 
+ ':' + seconds
, 
 960            'description': "US Date + 24Hr Time\n(w/hypens)" 
 962        "USDATETIMEMMDDYYYY/HHMM": { 
 963            'mask': "##/##/#### ##:## AM", 
 964            'excludeChars': am_pm_exclude
, 
 965            'formatcodes': 'DF!', 
 966            'validRegex': '^' + months 
+ '/' + days 
+ '/' + '\d{4} ' + hours 
+ ':' + minutes 
+ ' (A|P)M', 
 967            'description': "US Date + Time\n(without seconds)" 
 969        "USDATE24HRTIMEMMDDYYYY/HHMM": { 
 970            'mask': "##/##/#### ##:##", 
 972            'validRegex': '^' + months 
+ '/' + days 
+ '/' + '\d{4} ' + milhours 
+ ':' + minutes
, 
 973            'description': "US Date + 24Hr Time\n(without seconds)" 
 975        "USDATETIMEMMDDYYYY-HHMM": { 
 976            'mask': "##-##-#### ##:## AM", 
 977            'excludeChars': am_pm_exclude
, 
 978            'formatcodes': 'DF!', 
 979            'validRegex': '^' + months 
+ '-' + days 
+ '-' + '\d{4} ' + hours 
+ ':' + minutes 
+ ' (A|P)M', 
 980            'description': "US Date + Time\n(w/hypens and w/o secs)" 
 982        "USDATE24HRTIMEMMDDYYYY-HHMM": { 
 983            'mask': "##-##-#### ##:##", 
 985            'validRegex': '^' + months 
+ '-' + days 
+ '-' + '\d{4} ' + milhours 
+ ':' + minutes
, 
 986            'description': "US Date + 24Hr Time\n(w/hyphens and w/o seconds)" 
 989            'mask': "##/##/####", 
 991            'validRegex': '^' + months 
+ '/' + days 
+ '/' + '\d{4}', 
 992            'description': "US Date\n(MMDDYYYY)" 
 997            'validRegex': '^' + months 
+ '/' + days 
+ '/\d\d', 
 998            'description': "US Date\n(MMDDYY)" 
1000        "USDATEMMDDYYYY-": { 
1001            'mask': "##-##-####", 
1002            'formatcodes': 'DF', 
1003            'validRegex': '^' + months 
+ '-' + days 
+ '-' +'\d{4}', 
1004            'description': "MM-DD-YYYY" 
1007        "EUDATEYYYYMMDD/": { 
1008            'mask': "####/##/##", 
1009            'formatcodes': 'DF', 
1010            'validRegex': '^' + '\d{4}'+ '/' + months 
+ '/' + days
, 
1011            'description': "YYYY/MM/DD" 
1013        "EUDATEYYYYMMDD.": { 
1014            'mask': "####.##.##", 
1015            'formatcodes': 'DF', 
1016            'validRegex': '^' + '\d{4}'+ '.' + months 
+ '.' + days
, 
1017            'description': "YYYY.MM.DD" 
1019        "EUDATEDDMMYYYY/": { 
1020            'mask': "##/##/####", 
1021            'formatcodes': 'DF', 
1022            'validRegex': '^' + days 
+ '/' + months 
+ '/' + '\d{4}', 
1023            'description': "DD/MM/YYYY" 
1025        "EUDATEDDMMYYYY.": { 
1026            'mask': "##.##.####", 
1027            'formatcodes': 'DF', 
1028            'validRegex': '^' + days 
+ '.' + months 
+ '.' + '\d{4}', 
1029            'description': "DD.MM.YYYY" 
1031        "EUDATEDDMMMYYYY.": { 
1032            'mask': "##.CCC.####", 
1033            'formatcodes': 'DF', 
1034            'validRegex': '^' + days 
+ '.' + charmonths 
+ '.' + '\d{4}', 
1035            'description': "DD.Month.YYYY" 
1037        "EUDATEDDMMMYYYY/": { 
1038            'mask': "##/CCC/####", 
1039            'formatcodes': 'DF', 
1040            'validRegex': '^' + days 
+ '/' + charmonths 
+ '/' + '\d{4}', 
1041            'description': "DD/Month/YYYY" 
1044        "EUDATETIMEYYYYMMDD/HHMMSS": { 
1045            'mask': "####/##/## ##:##:## AM", 
1046            'excludeChars': am_pm_exclude
, 
1047            'formatcodes': 'DF!', 
1048            'validRegex': '^' + '\d{4}'+ '/' + months 
+ '/' + days 
+ ' ' + hours 
+ ':' + minutes 
+ ':' + seconds 
+ ' (A|P)M', 
1049            'description': "YYYY/MM/DD HH:MM:SS" 
1051        "EUDATETIMEYYYYMMDD.HHMMSS": { 
1052            'mask': "####.##.## ##:##:## AM", 
1053            'excludeChars': am_pm_exclude
, 
1054            'formatcodes': 'DF!', 
1055            'validRegex': '^' + '\d{4}'+ '.' + months 
+ '.' + days 
+ ' ' + hours 
+ ':' + minutes 
+ ':' + seconds 
+ ' (A|P)M', 
1056            'description': "YYYY.MM.DD HH:MM:SS" 
1058        "EUDATETIMEDDMMYYYY/HHMMSS": { 
1059            'mask': "##/##/#### ##:##:## AM", 
1060            'excludeChars': am_pm_exclude
, 
1061            'formatcodes': 'DF!', 
1062            'validRegex': '^' + days 
+ '/' + months 
+ '/' + '\d{4} ' + hours 
+ ':' + minutes 
+ ':' + seconds 
+ ' (A|P)M', 
1063            'description': "DD/MM/YYYY HH:MM:SS" 
1065        "EUDATETIMEDDMMYYYY.HHMMSS": { 
1066            'mask': "##.##.#### ##:##:## AM", 
1067            'excludeChars': am_pm_exclude
, 
1068            'formatcodes': 'DF!', 
1069            'validRegex': '^' + days 
+ '.' + months 
+ '.' + '\d{4} ' + hours 
+ ':' + minutes 
+ ':' + seconds 
+ ' (A|P)M', 
1070            'description': "DD.MM.YYYY HH:MM:SS" 
1073        "EUDATETIMEYYYYMMDD/HHMM": { 
1074            'mask': "####/##/## ##:## AM", 
1075            'excludeChars': am_pm_exclude
, 
1076            'formatcodes': 'DF!', 
1077            'validRegex': '^' + '\d{4}'+ '/' + months 
+ '/' + days 
+ ' ' + hours 
+ ':' + minutes 
+ ' (A|P)M', 
1078            'description': "YYYY/MM/DD HH:MM" 
1080        "EUDATETIMEYYYYMMDD.HHMM": { 
1081            'mask': "####.##.## ##:## AM", 
1082            'excludeChars': am_pm_exclude
, 
1083            'formatcodes': 'DF!', 
1084            'validRegex': '^' + '\d{4}'+ '.' + months 
+ '.' + days 
+ ' ' + hours 
+ ':' + minutes 
+ ' (A|P)M', 
1085            'description': "YYYY.MM.DD HH:MM" 
1087        "EUDATETIMEDDMMYYYY/HHMM": { 
1088            'mask': "##/##/#### ##:## AM", 
1089            'excludeChars': am_pm_exclude
, 
1090            'formatcodes': 'DF!', 
1091            'validRegex': '^' + days 
+ '/' + months 
+ '/' + '\d{4} ' + hours 
+ ':' + minutes 
+ ' (A|P)M', 
1092            'description': "DD/MM/YYYY HH:MM" 
1094        "EUDATETIMEDDMMYYYY.HHMM": { 
1095            'mask': "##.##.#### ##:## AM", 
1096            'excludeChars': am_pm_exclude
, 
1097            'formatcodes': 'DF!', 
1098            'validRegex': '^' + days 
+ '.' + months 
+ '.' + '\d{4} ' + hours 
+ ':' + minutes 
+ ' (A|P)M', 
1099            'description': "DD.MM.YYYY HH:MM" 
1102        "EUDATE24HRTIMEYYYYMMDD/HHMMSS": { 
1103            'mask': "####/##/## ##:##:##", 
1104            'formatcodes': 'DF', 
1105            'validRegex': '^' + '\d{4}'+ '/' + months 
+ '/' + days 
+ ' ' + milhours 
+ ':' + minutes 
+ ':' + seconds
, 
1106            'description': "YYYY/MM/DD 24Hr Time" 
1108        "EUDATE24HRTIMEYYYYMMDD.HHMMSS": { 
1109            'mask': "####.##.## ##:##:##", 
1110            'formatcodes': 'DF', 
1111            'validRegex': '^' + '\d{4}'+ '.' + months 
+ '.' + days 
+ ' ' + milhours 
+ ':' + minutes 
+ ':' + seconds
, 
1112            'description': "YYYY.MM.DD 24Hr Time" 
1114        "EUDATE24HRTIMEDDMMYYYY/HHMMSS": { 
1115            'mask': "##/##/#### ##:##:##", 
1116            'formatcodes': 'DF', 
1117            'validRegex': '^' + days 
+ '/' + months 
+ '/' + '\d{4} ' + milhours 
+ ':' + minutes 
+ ':' + seconds
, 
1118            'description': "DD/MM/YYYY 24Hr Time" 
1120        "EUDATE24HRTIMEDDMMYYYY.HHMMSS": { 
1121            'mask': "##.##.#### ##:##:##", 
1122            'formatcodes': 'DF', 
1123            'validRegex': '^' + days 
+ '.' + months 
+ '.' + '\d{4} ' + milhours 
+ ':' + minutes 
+ ':' + seconds
, 
1124            'description': "DD.MM.YYYY 24Hr Time" 
1126        "EUDATE24HRTIMEYYYYMMDD/HHMM": { 
1127            'mask': "####/##/## ##:##", 
1128            'formatcodes': 'DF','validRegex': '^' + '\d{4}'+ '/' + months 
+ '/' + days 
+ ' ' + milhours 
+ ':' + minutes
, 
1129            'description': "YYYY/MM/DD 24Hr Time\n(w/o seconds)" 
1131        "EUDATE24HRTIMEYYYYMMDD.HHMM": { 
1132            'mask': "####.##.## ##:##", 
1133            'formatcodes': 'DF', 
1134            'validRegex': '^' + '\d{4}'+ '.' + months 
+ '.' + days 
+ ' ' + milhours 
+ ':' + minutes
, 
1135            'description': "YYYY.MM.DD 24Hr Time\n(w/o seconds)" 
1137        "EUDATE24HRTIMEDDMMYYYY/HHMM": { 
1138            'mask': "##/##/#### ##:##", 
1139            'formatcodes': 'DF', 
1140            'validRegex': '^' + days 
+ '/' + months 
+ '/' + '\d{4} ' + milhours 
+ ':' + minutes
, 
1141            'description': "DD/MM/YYYY 24Hr Time\n(w/o seconds)" 
1143        "EUDATE24HRTIMEDDMMYYYY.HHMM": { 
1144            'mask': "##.##.#### ##:##", 
1145            'formatcodes': 'DF', 
1146            'validRegex': '^' + days 
+ '.' + months 
+ '.' + '\d{4} ' + milhours 
+ ':' + minutes
, 
1147            'description': "DD.MM.YYYY 24Hr Time\n(w/o seconds)" 
1151            'mask': "##:##:## AM", 
1152            'excludeChars': am_pm_exclude
, 
1153            'formatcodes': 'TF!', 
1154            'validRegex': '^' + hours 
+ ':' + minutes 
+ ':' + seconds 
+ ' (A|P)M', 
1155            'description': "HH:MM:SS (A|P)M\n(see TimeCtrl)" 
1159            'excludeChars': am_pm_exclude
, 
1160            'formatcodes': 'TF!', 
1161            'validRegex': '^' + hours 
+ ':' + minutes 
+ ' (A|P)M', 
1162            'description': "HH:MM (A|P)M\n(see TimeCtrl)" 
1166            'formatcodes': 'TF', 
1167            'validRegex': '^' + milhours 
+ ':' + minutes 
+ ':' + seconds
, 
1168            'description': "24Hr HH:MM:SS\n(see TimeCtrl)" 
1172            'formatcodes': 'TF', 
1173            'validRegex': '^' + milhours 
+ ':' + minutes
, 
1174            'description': "24Hr HH:MM\n(see TimeCtrl)" 
1177            'mask': "###-##-####", 
1179            'validRegex': "\d{3}-\d{2}-\d{4}", 
1180            'description': "Social Sec#" 
1183            'mask': "####-####-####-####", 
1185            'validRegex': "\d{4}-\d{4}-\d{4}-\d{4}", 
1186            'description': "Credit Card" 
1191            'validRegex': "^" + months 
+ "/\d\d", 
1192            'description': "Expiration MM/YY" 
1197            'validRegex': "^\d{5}", 
1198            'description': "US 5-digit zip code" 
1201            'mask': "#####-####", 
1203            'validRegex': "\d{5}-(\s{4}|\d{4})", 
1204            'description': "US zip+4 code" 
1209            'validRegex': "^0.\d\d", 
1210            'description': "Percentage" 
1215            'validRegex': "^[1-9]{1}  |[1-9][0-9] |1[0|1|2][0-9]", 
1216            'description': "Age" 
1219            'mask': "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX", 
1220            'excludeChars': " \\/*&%$#!+='\"", 
1221            'formatcodes': "F>", 
1222            '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}\]) *$", 
1223            'description': "Email address" 
1226            'mask': "###.###.###.###", 
1227            'formatcodes': 'F_Sr', 
1228            '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}", 
1229            'description': "IP Address\n(see IpAddrCtrl)" 
1233 # build demo-friendly dictionary of descriptions of autoformats 
1235 for key
, value 
in masktags
.items(): 
1236     autoformats
.append((key
, value
['description'])) 
1239 ## ---------- ---------- ---------- ---------- ---------- ---------- ---------- 
1243     This class manages the individual fields in a masked edit control. 
1244     Each field has a zero-based index, indicating its position in the 
1245     control, an extent, an associated mask, and a plethora of optional 
1246     parameters.  Fields can be instantiated and then associated with 
1247     parent masked controls, in order to provide field-specific configuration. 
1248     Alternatively, fields will be implicitly created by the parent control 
1249     if not provided at construction, at which point, the fields can then 
1250     manipulated by the controls .SetFieldParameters() method. 
1253               'index': None,                    ## which field of mask; set by parent control. 
1254               'mask': "",                       ## mask chars for this field 
1255               'extent': (),                     ## (edit start, edit_end) of field; set by parent control. 
1256               'formatcodes':  "",               ## codes indicating formatting options for the control 
1257               'fillChar':     ' ',              ## used as initial value for each mask position if initial value is not given 
1258               'groupChar':    ',',              ## used with numeric fields; indicates what char groups 3-tuple digits 
1259               'decimalChar':  '.',              ## used with numeric fields; indicates what char separates integer from fraction 
1260               'shiftDecimalChar': '>',          ## used with numeric fields, indicates what is above the decimal point char on keyboard 
1261               'useParensForNegatives': False,   ## used with numeric fields, indicates that () should be used vs. - to show negative numbers. 
1262               'defaultValue': "",               ## use if you want different positional defaults vs. all the same fillChar 
1263               'excludeChars': "",               ## optional string of chars to exclude even if main mask type does 
1264               'includeChars': "",               ## optional string of chars to allow even if main mask type doesn't 
1265               'validRegex':   "",               ## optional regular expression to use to validate the control 
1266               'validRange':   (),               ## Optional hi-low range for numerics 
1267               'choices':    [],                 ## Optional list for character expressions 
1268               'choiceRequired': False,          ## If choices supplied this specifies if valid value must be in the list 
1269               'compareNoCase': False,           ## Optional flag to indicate whether or not to use case-insensitive list search 
1270               'autoSelect': False,              ## Set to True to try auto-completion on each keystroke: 
1271               'validFunc': None,                ## Optional function for defining additional, possibly dynamic validation constraints on contrl 
1272               'validRequired': False,           ## Set to True to disallow input that results in an invalid value 
1273               'emptyInvalid':  False,           ## Set to True to make EMPTY = INVALID 
1274               'description': "",                ## primarily for autoformats, but could be useful elsewhere 
1277     # This list contains all parameters that when set at the control level should 
1278     # propagate down to each field: 
1279     propagating_params 
= ('fillChar', 'groupChar', 'decimalChar','useParensForNegatives', 
1280                           'compareNoCase', 'emptyInvalid', 'validRequired') 
1282     def __init__(self
, **kwargs
): 
1284         This is the "constructor" for setting up parameters for fields. 
1285         a field_index of -1 is used to indicate "the entire control." 
1287 ####        dbg('Field::Field', indent=1) 
1288         # Validate legitimate set of parameters: 
1289         for key 
in kwargs
.keys(): 
1290             if key 
not in Field
.valid_params
.keys(): 
1292                 raise TypeError('invalid parameter "%s"' % (key
)) 
1294         # Set defaults for each parameter for this instance, and fully 
1295         # populate initial parameter list for configuration: 
1296         for key
, value 
in Field
.valid_params
.items(): 
1297             setattr(self
, '_' + key
, copy
.copy(value
)) 
1298             if not kwargs
.has_key(key
): 
1299                 kwargs
[key
] = copy
.copy(value
) 
1301         self
._autoCompleteIndex 
= -1 
1302         self
._SetParameters
(**kwargs
) 
1303         self
._ValidateParameters
(**kwargs
) 
1308     def _SetParameters(self
, **kwargs
): 
1310         This function can be used to set individual or multiple parameters for 
1311         a masked edit field parameter after construction. 
1314 ##        dbg('maskededit.Field::_SetParameters', indent=1) 
1315         # Validate keyword arguments: 
1316         for key 
in kwargs
.keys(): 
1317             if key 
not in Field
.valid_params
.keys(): 
1318 ##                dbg(indent=0, suspend=0) 
1319                 raise AttributeError('invalid keyword argument "%s"' % key
) 
1321 ##        if self._index is not None: dbg('field index:', self._index) 
1322 ##        dbg('parameters:', indent=1) 
1323         for key
, value 
in kwargs
.items(): 
1324 ##            dbg('%s:' % key, value) 
1329         old_fillChar 
= self
._fillChar   
# store so we can change choice lists accordingly if it changes 
1331         # First, Assign all parameters specified: 
1332         for key 
in Field
.valid_params
.keys(): 
1333             if kwargs
.has_key(key
): 
1334                 setattr(self
, '_' + key
, kwargs
[key
] ) 
1336         if kwargs
.has_key('formatcodes'):   # (set/changed) 
1337             self
._forceupper  
= '!' in self
._formatcodes
 
1338             self
._forcelower  
= '^' in self
._formatcodes
 
1339             self
._groupdigits 
= ',' in self
._formatcodes
 
1340             self
._okSpaces    
= '_' in self
._formatcodes
 
1341             self
._padZero     
= '0' in self
._formatcodes
 
1342             self
._autofit     
= 'F' in self
._formatcodes
 
1343             self
._insertRight 
= 'r' in self
._formatcodes
 
1344             self
._allowInsert 
= '>' in self
._formatcodes
 
1345             self
._alignRight  
= 'R' in self
._formatcodes 
or 'r' in self
._formatcodes
 
1346             self
._moveOnFieldFull 
= not '<' in self
._formatcodes
 
1347             self
._selectOnFieldEntry 
= 'S' in self
._formatcodes
 
1349             if kwargs
.has_key('groupChar'): 
1350                 self
._groupChar 
= kwargs
['groupChar'] 
1351             if kwargs
.has_key('decimalChar'): 
1352                 self
._decimalChar 
= kwargs
['decimalChar'] 
1353             if kwargs
.has_key('shiftDecimalChar'): 
1354                 self
._shiftDecimalChar 
= kwargs
['shiftDecimalChar'] 
1356         if kwargs
.has_key('formatcodes') or kwargs
.has_key('validRegex'): 
1357             self
._regexMask   
= 'V' in self
._formatcodes 
and self
._validRegex
 
1359         if kwargs
.has_key('fillChar'): 
1360             self
._old
_fillChar 
= old_fillChar
 
1361 ####            dbg("self._old_fillChar: '%s'" % self._old_fillChar) 
1363         if kwargs
.has_key('mask') or kwargs
.has_key('validRegex'):  # (set/changed) 
1364             self
._isInt 
= _isInteger(self
._mask
) 
1365 ##            dbg('isInt?', self._isInt, 'self._mask:"%s"' % self._mask) 
1367 ##        dbg(indent=0, suspend=0) 
1370     def _ValidateParameters(self
, **kwargs
): 
1372         This function can be used to validate individual or multiple parameters for 
1373         a masked edit field parameter after construction. 
1376 ##        dbg('maskededit.Field::_ValidateParameters', indent=1) 
1377 ##        if self._index is not None: dbg('field index:', self._index) 
1378 ####        dbg('parameters:', indent=1) 
1379 ##        for key, value in kwargs.items(): 
1380 ####            dbg('%s:' % key, value) 
1382 ####        dbg("self._old_fillChar: '%s'" % self._old_fillChar) 
1384         # Verify proper numeric format params: 
1385         if self
._groupdigits 
and self
._groupChar 
== self
._decimalChar
: 
1386 ##            dbg(indent=0, suspend=0) 
1387             raise AttributeError("groupChar '%s' cannot be the same as decimalChar '%s'" % (self
._groupChar
, self
._decimalChar
)) 
1390         # Now go do validation, semantic and inter-dependency parameter processing: 
1391         if kwargs
.has_key('choices') or kwargs
.has_key('compareNoCase') or kwargs
.has_key('choiceRequired'): # (set/changed) 
1393             self
._compareChoices 
= [choice
.strip() for choice 
in self
._choices
] 
1395             if self
._compareNoCase 
and self
._choices
: 
1396                 self
._compareChoices 
= [item
.lower() for item 
in self
._compareChoices
] 
1398             if kwargs
.has_key('choices'): 
1399                 self
._autoCompleteIndex 
= -1 
1402         if kwargs
.has_key('validRegex'):    # (set/changed) 
1403             if self
._validRegex
: 
1405                     if self
._compareNoCase
: 
1406                         self
._filter 
= re
.compile(self
._validRegex
, re
.IGNORECASE
) 
1408                         self
._filter 
= re
.compile(self
._validRegex
) 
1410 ##                    dbg(indent=0, suspend=0) 
1411                     raise TypeError('%s: validRegex "%s" not a legal regular expression' % (str(self
._index
), self
._validRegex
)) 
1415         if kwargs
.has_key('validRange'):    # (set/changed) 
1416             self
._hasRange  
= False 
1419             if self
._validRange
: 
1420                 if type(self
._validRange
) != types
.TupleType 
or len( self
._validRange 
)!= 2 or self
._validRange
[0] > self
._validRange
[1]: 
1421 ##                    dbg(indent=0, suspend=0) 
1422                     raise TypeError('%s: validRange %s parameter must be tuple of form (a,b) where a <= b' 
1423                                     % (str(self
._index
), repr(self
._validRange
)) ) 
1425                 self
._hasRange  
= True 
1426                 self
._rangeLow  
= self
._validRange
[0] 
1427                 self
._rangeHigh 
= self
._validRange
[1] 
1429         if kwargs
.has_key('choices') or (len(self
._choices
) and len(self
._choices
[0]) != len(self
._mask
)):       # (set/changed) 
1430             self
._hasList   
= False 
1431             if self
._choices 
and type(self
._choices
) not in (types
.TupleType
, types
.ListType
): 
1432 ##                dbg(indent=0, suspend=0) 
1433                 raise TypeError('%s: choices must be a sequence of strings' % str(self
._index
)) 
1434             elif len( self
._choices
) > 0: 
1435                 for choice 
in self
._choices
: 
1436                     if type(choice
) not in (types
.StringType
, types
.UnicodeType
): 
1437 ##                        dbg(indent=0, suspend=0) 
1438                         raise TypeError('%s: choices must be a sequence of strings' % str(self
._index
)) 
1440                 length 
= len(self
._mask
) 
1441 ##                dbg('len(%s)' % self._mask, length, 'len(self._choices):', len(self._choices), 'length:', length, 'self._alignRight?', self._alignRight) 
1442                 if len(self
._choices
) and length
: 
1443                     if len(self
._choices
[0]) > length
: 
1444                         # changed mask without respecifying choices; readjust the width as appropriate: 
1445                         self
._choices 
= [choice
.strip() for choice 
in self
._choices
] 
1446                     if self
._alignRight
: 
1447                         self
._choices 
= [choice
.rjust( length 
) for choice 
in self
._choices
] 
1449                         self
._choices 
= [choice
.ljust( length 
) for choice 
in self
._choices
] 
1450 ##                    dbg('aligned choices:', self._choices) 
1452                 if hasattr(self
, '_template'): 
1453                     # Verify each choice specified is valid: 
1454                     for choice 
in self
._choices
: 
1455                         if self
.IsEmpty(choice
) and not self
._validRequired
: 
1456                             # allow empty values even if invalid, (just colored differently) 
1458                         if not self
.IsValid(choice
): 
1459 ##                            dbg(indent=0, suspend=0) 
1460                             raise ValueError('%s: "%s" is not a valid value for the control as specified.' % (str(self
._index
), choice
)) 
1461                 self
._hasList 
= True 
1463 ####        dbg("kwargs.has_key('fillChar')?", kwargs.has_key('fillChar'), "len(self._choices) > 0?", len(self._choices) > 0) 
1464 ####        dbg("self._old_fillChar:'%s'" % self._old_fillChar, "self._fillChar: '%s'" % self._fillChar) 
1465         if kwargs
.has_key('fillChar') and len(self
._choices
) > 0: 
1466             if kwargs
['fillChar'] != ' ': 
1467                 self
._choices 
= [choice
.replace(' ', self
._fillChar
) for choice 
in self
._choices
] 
1469                 self
._choices 
= [choice
.replace(self
._old
_fillChar
, self
._fillChar
) for choice 
in self
._choices
] 
1470 ##            dbg('updated choices:', self._choices) 
1473         if kwargs
.has_key('autoSelect') and kwargs
['autoSelect']: 
1474             if not self
._hasList
: 
1475 ##                dbg('no list to auto complete; ignoring "autoSelect=True"') 
1476                 self
._autoSelect 
= False 
1478         # reset field validity assumption: 
1480 ##        dbg(indent=0, suspend=0) 
1483     def _GetParameter(self
, paramname
): 
1485         Routine for retrieving the value of any given parameter 
1487         if Field
.valid_params
.has_key(paramname
): 
1488             return getattr(self
, '_' + paramname
) 
1490             TypeError('Field._GetParameter: invalid parameter "%s"' % key
) 
1493     def IsEmpty(self
, slice): 
1495         Indicates whether the specified slice is considered empty for the 
1498 ##        dbg('Field::IsEmpty("%s")' % slice, indent=1) 
1499         if not hasattr(self
, '_template'): 
1501             raise AttributeError('_template') 
1503 ##        dbg('self._template: "%s"' % self._template) 
1504 ##        dbg('self._defaultValue: "%s"' % str(self._defaultValue)) 
1505         if slice == self
._template 
and not self
._defaultValue
: 
1509         elif slice == self
._template
: 
1511             for pos 
in range(len(self
._template
)): 
1512 ####                dbg('slice[%(pos)d] != self._fillChar?' %locals(), slice[pos] != self._fillChar[pos]) 
1513                 if slice[pos
] not in (' ', self
._fillChar
): 
1516 ##            dbg("IsEmpty? %(empty)d (do all mask chars == fillChar?)" % locals(), indent=0) 
1519 ##            dbg("IsEmpty? 0 (slice doesn't match template)", indent=0) 
1523     def IsValid(self
, slice): 
1525         Indicates whether the specified slice is considered a valid value for the 
1529 ##        dbg('Field[%s]::IsValid("%s")' % (str(self._index), slice), indent=1) 
1530         valid 
= True    # assume true to start 
1532         if self
.IsEmpty(slice): 
1533 ##            dbg(indent=0, suspend=0) 
1534             if self
._emptyInvalid
: 
1539         elif self
._hasList 
and self
._choiceRequired
: 
1540 ##            dbg("(member of list required)") 
1541             # do case-insensitive match on list; strip surrounding whitespace from slice (already done for choices): 
1542             if self
._fillChar 
!= ' ': 
1543                 slice = slice.replace(self
._fillChar
, ' ') 
1544 ##                dbg('updated slice:"%s"' % slice) 
1545             compareStr 
= slice.strip() 
1547             if self
._compareNoCase
: 
1548                 compareStr 
= compareStr
.lower() 
1549             valid 
= compareStr 
in self
._compareChoices
 
1551         elif self
._hasRange 
and not self
.IsEmpty(slice): 
1552 ##            dbg('validating against range') 
1554                 # allow float as well as int ranges (int comparisons for free.) 
1555                 valid 
= self
._rangeLow 
<= float(slice) <= self
._rangeHigh
 
1559         elif self
._validRegex 
and self
._filter
: 
1560 ##            dbg('validating against regex') 
1561             valid 
= (re
.match( self
._filter
, slice) is not None) 
1563         if valid 
and self
._validFunc
: 
1564 ##            dbg('validating against supplied function') 
1565             valid 
= self
._validFunc
(slice) 
1566 ##        dbg('valid?', valid, indent=0, suspend=0) 
1570     def _AdjustField(self
, slice): 
1571         """ 'Fixes' an integer field. Right or left-justifies, as required.""" 
1572 ##        dbg('Field::_AdjustField("%s")' % slice, indent=1) 
1573         length 
= len(self
._mask
) 
1574 ####        dbg('length(self._mask):', length) 
1575 ####        dbg('self._useParensForNegatives?', self._useParensForNegatives) 
1577             if self
._useParensForNegatives
: 
1578                 signpos 
= slice.find('(') 
1579                 right_signpos 
= slice.find(')') 
1580                 intStr 
= slice.replace('(', '').replace(')', '')    # drop sign, if any 
1582                 signpos 
= slice.find('-') 
1583                 intStr 
= slice.replace( '-', '' )                   # drop sign, if any 
1586             intStr 
= intStr
.replace(' ', '')                        # drop extra spaces 
1587             intStr 
= string
.replace(intStr
,self
._fillChar
,"")       # drop extra fillchars 
1588             intStr 
= string
.replace(intStr
,"-","")                  # drop sign, if any 
1589             intStr 
= string
.replace(intStr
, self
._groupChar
, "")    # lose commas/dots 
1590 ####            dbg('intStr:"%s"' % intStr) 
1591             start
, end 
= self
._extent
 
1592             field_len 
= end 
- start
 
1593             if not self
._padZero 
and len(intStr
) != field_len 
and intStr
.strip(): 
1594                 intStr 
= str(long(intStr
)) 
1595 ####            dbg('raw int str: "%s"' % intStr) 
1596 ####            dbg('self._groupdigits:', self._groupdigits, 'self._formatcodes:', self._formatcodes) 
1597             if self
._groupdigits
: 
1600                 for i 
in range(len(intStr
)-1, -1, -1): 
1601                     new 
= intStr
[i
] + new
 
1603                         new 
= self
._groupChar 
+ new
 
1605                 if new 
and new
[0] == self
._groupChar
: 
1607                 if len(new
) <= length
: 
1608                     # expanded string will still fit and leave room for sign: 
1610                 # else... leave it without the commas... 
1612 ##            dbg('padzero?', self._padZero) 
1613 ##            dbg('len(intStr):', len(intStr), 'field length:', length) 
1614             if self
._padZero 
and len(intStr
) < length
: 
1615                 intStr 
= '0' * (length 
- len(intStr
)) + intStr
 
1616                 if signpos 
!= -1:   # we had a sign before; restore it 
1617                     if self
._useParensForNegatives
: 
1618                         intStr 
= '(' + intStr
[1:] 
1619                         if right_signpos 
!= -1: 
1622                         intStr 
= '-' + intStr
[1:] 
1623             elif signpos 
!= -1 and slice[0:signpos
].strip() == '':    # - was before digits 
1624                 if self
._useParensForNegatives
: 
1625                     intStr 
= '(' + intStr
 
1626                     if right_signpos 
!= -1: 
1629                     intStr 
= '-' + intStr
 
1630             elif right_signpos 
!= -1: 
1631                 # must have had ')' but '(' was before field; re-add ')' 
1635         slice = slice.strip() # drop extra spaces 
1637         if self
._alignRight
:     ## Only if right-alignment is enabled 
1638             slice = slice.rjust( length 
) 
1640             slice = slice.ljust( length 
) 
1641         if self
._fillChar 
!= ' ': 
1642             slice = slice.replace(' ', self
._fillChar
) 
1643 ##        dbg('adjusted slice: "%s"' % slice, indent=0) 
1647 ## ---------- ---------- ---------- ---------- ---------- ---------- ---------- 
1649 class MaskedEditMixin
: 
1651     This class allows us to abstract the masked edit functionality that could 
1652     be associated with any text entry control. (eg. wx.TextCtrl, wx.ComboBox, etc.) 
1653     It forms the basis for all of the lib.masked controls. 
1655     valid_ctrl_params 
= { 
1656               'mask': 'XXXXXXXXXXXXX',          ## mask string for formatting this control 
1657               'autoformat':   "",               ## optional auto-format code to set format from masktags dictionary 
1658               'fields': {},                     ## optional list/dictionary of maskededit.Field class instances, indexed by position in mask 
1659               'datestyle':    'MDY',            ## optional date style for date-type values. Can trigger autocomplete year 
1660               'autoCompleteKeycodes': [],       ## Optional list of additional keycodes which will invoke field-auto-complete 
1661               'useFixedWidthFont': True,        ## Use fixed-width font instead of default for base control 
1662               'defaultEncoding': 'latin1',      ## optional argument to indicate unicode codec to use (unicode ctrls only) 
1663               'retainFieldValidation': False,   ## Set this to true if setting control-level parameters independently, 
1664                                                 ## from field validation constraints 
1665               'emptyBackgroundColour': "White", 
1666               'validBackgroundColour': "White", 
1667               'invalidBackgroundColour': "Yellow", 
1668               'foregroundColour': "Black", 
1669               'signedForegroundColour': "Red", 
1673     def __init__(self
, name 
= 'MaskedEdit', **kwargs
): 
1675         This is the "constructor" for setting up the mixin variable parameters for the composite class. 
1680         # set up flag for doing optional things to base control if possible 
1681         if not hasattr(self
, 'controlInitialized'): 
1682             self
.controlInitialized 
= False 
1684         # Set internal state var for keeping track of whether or not a character 
1685         # action results in a modification of the control, since .SetValue() 
1686         # doesn't modify the base control's internal state: 
1687         self
.modified 
= False 
1688         self
._previous
_mask 
= None 
1690         # Validate legitimate set of parameters: 
1691         for key 
in kwargs
.keys(): 
1692             if key
.replace('Color', 'Colour') not in MaskedEditMixin
.valid_ctrl_params
.keys() + Field
.valid_params
.keys(): 
1693                 raise TypeError('%s: invalid parameter "%s"' % (name
, key
)) 
1695         ## Set up dictionary that can be used by subclasses to override or add to default 
1696         ## behavior for individual characters.  Derived subclasses needing to change 
1697         ## default behavior for keys can either redefine the default functions for the 
1698         ## common keys or add functions for specific keys to this list.  Each function 
1699         ## added should take the key event as argument, and return False if the key 
1700         ## requires no further processing. 
1702         ## Initially populated with navigation and function control keys: 
1703         self
._keyhandlers 
= { 
1704             # default navigation keys and handlers: 
1705             wx
.WXK_BACK
:   self
._OnErase
, 
1706             wx
.WXK_LEFT
:   self
._OnArrow
, 
1707             wx
.WXK_RIGHT
:  self
._OnArrow
, 
1708             wx
.WXK_UP
:     self
._OnAutoCompleteField
, 
1709             wx
.WXK_DOWN
:   self
._OnAutoCompleteField
, 
1710             wx
.WXK_TAB
:    self
._OnChangeField
, 
1711             wx
.WXK_HOME
:   self
._OnHome
, 
1712             wx
.WXK_END
:    self
._OnEnd
, 
1713             wx
.WXK_RETURN
: self
._OnReturn
, 
1714             wx
.WXK_PRIOR
:  self
._OnAutoCompleteField
, 
1715             wx
.WXK_NEXT
:   self
._OnAutoCompleteField
, 
1717             # default function control keys and handlers: 
1718             wx
.WXK_DELETE
: self
._OnDelete
, 
1719             wx
.WXK_INSERT
: self
._OnInsert
, 
1720             WXK_CTRL_A
: self
._OnCtrl
_A
, 
1721             WXK_CTRL_C
: self
._OnCtrl
_C
, 
1722             WXK_CTRL_S
: self
._OnCtrl
_S
, 
1723             WXK_CTRL_V
: self
._OnCtrl
_V
, 
1724             WXK_CTRL_X
: self
._OnCtrl
_X
, 
1725             WXK_CTRL_Z
: self
._OnCtrl
_Z
, 
1728         ## bind standard navigational and control keycodes to this instance, 
1729         ## so that they can be augmented and/or changed in derived classes: 
1730         self
._nav 
= list(nav
) 
1731         self
._control 
= list(control
) 
1733         ## Dynamically evaluate and store string constants for mask chars 
1734         ## so that locale settings can be made after this module is imported 
1735         ## and the controls created after that is done can allow the 
1736         ## appropriate characters: 
1737         self
.maskchardict  
= { 
1739             'A': string
.uppercase
, 
1740             'a': string
.lowercase
, 
1741             'X': string
.letters 
+ string
.punctuation 
+ string
.digits
, 
1742             'C': string
.letters
, 
1743             'N': string
.letters 
+ string
.digits
, 
1744             '&': string
.punctuation
, 
1748         ## self._ignoreChange is used by MaskedComboBox, because 
1749         ## of the hack necessary to determine the selection; it causes 
1750         ## EVT_TEXT messages from the combobox to be ignored if set. 
1751         self
._ignoreChange 
= False 
1753         # These are used to keep track of previous value, for undo functionality: 
1754         self
._curValue  
= None 
1755         self
._prevValue 
= None 
1759         # Set defaults for each parameter for this instance, and fully 
1760         # populate initial parameter list for configuration: 
1761         for key
, value 
in MaskedEditMixin
.valid_ctrl_params
.items(): 
1762             setattr(self
, '_' + key
, copy
.copy(value
)) 
1763             if not kwargs
.has_key(key
): 
1764 ####                dbg('%s: "%s"' % (key, repr(value))) 
1765                 kwargs
[key
] = copy
.copy(value
) 
1767         # Create a "field" that holds global parameters for control constraints 
1768         self
._ctrl
_constraints 
= self
._fields
[-1] = Field(index
=-1) 
1769         self
.SetCtrlParameters(**kwargs
) 
1773     def SetCtrlParameters(self
, **kwargs
): 
1775         This public function can be used to set individual or multiple masked edit 
1776         parameters after construction.  (See maskededit module overview for the list 
1777         of valid parameters.) 
1780 ##        dbg('MaskedEditMixin::SetCtrlParameters', indent=1) 
1781 ####        dbg('kwargs:', indent=1) 
1782 ##        for key, value in kwargs.items(): 
1783 ####            dbg(key, '=', value) 
1786         # Validate keyword arguments: 
1787         constraint_kwargs 
= {} 
1789         for key
, value 
in kwargs
.items(): 
1790             key 
= key
.replace('Color', 'Colour')    # for b-c, and standard wxPython spelling 
1791             if key 
not in MaskedEditMixin
.valid_ctrl_params
.keys() + Field
.valid_params
.keys(): 
1792 ##                dbg(indent=0, suspend=0) 
1793                 raise TypeError('Invalid keyword argument "%s" for control "%s"' % (key
, self
.name
)) 
1794             elif key 
in Field
.valid_params
.keys(): 
1795                 constraint_kwargs
[key
] = value
 
1797                 ctrl_kwargs
[key
] = value
 
1802         if ctrl_kwargs
.has_key('autoformat'): 
1803             autoformat 
= ctrl_kwargs
['autoformat'] 
1807         # handle "parochial name" backward compatibility: 
1808         if autoformat 
and autoformat
.find('MILTIME') != -1 and autoformat 
not in masktags
.keys(): 
1809             autoformat 
= autoformat
.replace('MILTIME', '24HRTIME') 
1811         if autoformat 
!= self
._autoformat 
and autoformat 
in masktags
.keys(): 
1812 ##            dbg('autoformat:', autoformat) 
1813             self
._autoformat                  
= autoformat
 
1814             mask                              
= masktags
[self
._autoformat
]['mask'] 
1815             # gather rest of any autoformat parameters: 
1816             for param
, value 
in masktags
[self
._autoformat
].items(): 
1817                 if param 
== 'mask': continue    # (must be present; already accounted for) 
1818                 constraint_kwargs
[param
] = value
 
1820         elif autoformat 
and not autoformat 
in masktags
.keys(): 
1821             raise AttributeError('invalid value for autoformat parameter: %s' % repr(autoformat
)) 
1823 ##            dbg('autoformat not selected') 
1824             if kwargs
.has_key('mask'): 
1825                 mask 
= kwargs
['mask'] 
1826 ##                dbg('mask:', mask) 
1828         ## Assign style flags 
1830 ##            dbg('preserving previous mask') 
1831             mask 
= self
._previous
_mask   
# preserve previous mask 
1833 ##            dbg('mask (re)set') 
1834             reset_args
['reset_mask'] = mask
 
1835             constraint_kwargs
['mask'] = mask
 
1837             # wipe out previous fields; preserve new control-level constraints 
1838             self
._fields 
= {-1: self._ctrl_constraints}
 
1841         if ctrl_kwargs
.has_key('fields'): 
1842             # do field parameter type validation, and conversion to internal dictionary 
1844             fields 
= ctrl_kwargs
['fields'] 
1845             if type(fields
) in (types
.ListType
, types
.TupleType
): 
1846                 for i 
in range(len(fields
)): 
1848                     if not isinstance(field
, Field
): 
1849 ##                        dbg(indent=0, suspend=0) 
1850                         raise AttributeError('invalid type for field parameter: %s' % repr(field
)) 
1851                     self
._fields
[i
] = field
 
1853             elif type(fields
) == types
.DictionaryType
: 
1854                 for index
, field 
in fields
.items(): 
1855                     if not isinstance(field
, Field
): 
1856 ##                        dbg(indent=0, suspend=0) 
1857                         raise AttributeError('invalid type for field parameter: %s' % repr(field
)) 
1858                     self
._fields
[index
] = field
 
1860 ##                dbg(indent=0, suspend=0) 
1861                 raise AttributeError('fields parameter must be a list or dictionary; not %s' % repr(fields
)) 
1863         # Assign constraint parameters for entire control: 
1864 ####        dbg('control constraints:', indent=1) 
1865 ##        for key, value in constraint_kwargs.items(): 
1866 ####            dbg('%s:' % key, value) 
1869         # determine if changing parameters that should affect the entire control: 
1870         for key 
in MaskedEditMixin
.valid_ctrl_params
.keys(): 
1871             if key 
in ( 'mask', 'fields' ): continue    # (processed separately) 
1872             if ctrl_kwargs
.has_key(key
): 
1873                 setattr(self
, '_' + key
, ctrl_kwargs
[key
]) 
1875         # Validate color parameters, converting strings to named colors and validating 
1876         # result if appropriate: 
1877         for key 
in ('emptyBackgroundColour', 'invalidBackgroundColour', 'validBackgroundColour', 
1878                     'foregroundColour', 'signedForegroundColour'): 
1879             if ctrl_kwargs
.has_key(key
): 
1880                 if type(ctrl_kwargs
[key
]) in (types
.StringType
, types
.UnicodeType
): 
1881                     c 
= wx
.NamedColour(ctrl_kwargs
[key
]) 
1882                     if c
.Get() == (-1, -1, -1): 
1883                         raise TypeError('%s not a legal color specification for %s' % (repr(ctrl_kwargs
[key
]), key
)) 
1885                         # replace attribute with wxColour object: 
1886                         setattr(self
, '_' + key
, c
) 
1887                         # attach a python dynamic attribute to wxColour for debug printouts 
1888                         c
._name 
= ctrl_kwargs
[key
] 
1890                 elif type(ctrl_kwargs
[key
]) != type(wx
.BLACK
): 
1891                     raise TypeError('%s not a legal color specification for %s' % (repr(ctrl_kwargs
[key
]), key
)) 
1894 ##        dbg('self._retainFieldValidation:', self._retainFieldValidation) 
1895         if not self
._retainFieldValidation
: 
1896             # Build dictionary of any changing parameters which should be propagated to the 
1898             for arg 
in Field
.propagating_params
: 
1899 ####                dbg('kwargs.has_key(%s)?' % arg, kwargs.has_key(arg)) 
1900 ####                dbg('getattr(self._ctrl_constraints, _%s)?' % arg, getattr(self._ctrl_constraints, '_'+arg)) 
1901                 reset_args
[arg
] = kwargs
.has_key(arg
) and kwargs
[arg
] != getattr(self
._ctrl
_constraints
, '_'+arg
) 
1902 ####                dbg('reset_args[%s]?' % arg, reset_args[arg]) 
1904         # Set the control-level constraints: 
1905         self
._ctrl
_constraints
._SetParameters
(**constraint_kwargs
) 
1907         # This routine does the bulk of the interdependent parameter processing, determining 
1908         # the field extents of the mask if changed, resetting parameters as appropriate, 
1909         # determining the overall template value for the control, etc. 
1910         self
._configure
(mask
, **reset_args
) 
1912         # now that we've propagated the field constraints and mask portions to the 
1913         # various fields, validate the constraints 
1914         self
._ctrl
_constraints
._ValidateParameters
(**constraint_kwargs
) 
1916         # Validate that all choices for given fields are at least of the 
1917         # necessary length, and that they all would be valid pastes if pasted 
1918         # into their respective fields: 
1919 ####        dbg('validating choices') 
1920         self
._validateChoices
() 
1923         self
._autofit 
= self
._ctrl
_constraints
._autofit
 
1926         self
._isDate     
= 'D' in self
._ctrl
_constraints
._formatcodes 
and _isDateType(mask
) 
1927         self
._isTime     
= 'T' in self
._ctrl
_constraints
._formatcodes 
and _isTimeType(mask
) 
1929             # Set _dateExtent, used in date validation to locate date in string; 
1930             # always set as though year will be 4 digits, even if mask only has 
1931             # 2 digits, so we can always properly process the intended year for 
1932             # date validation (leap years, etc.) 
1933             if self
._mask
.find('CCC') != -1: self
._dateExtent 
= 11 
1934             else:                            self
._dateExtent 
= 10 
1936             self
._4digityear 
= len(self
._mask
) > 8 and self
._mask
[9] == '#' 
1938         if self
._isDate 
and self
._autoformat
: 
1939             # Auto-decide datestyle: 
1940             if self
._autoformat
.find('MDDY')    != -1: self
._datestyle 
= 'MDY' 
1941             elif self
._autoformat
.find('YMMD')  != -1: self
._datestyle 
= 'YMD' 
1942             elif self
._autoformat
.find('YMMMD') != -1: self
._datestyle 
= 'YMD' 
1943             elif self
._autoformat
.find('DMMY')  != -1: self
._datestyle 
= 'DMY' 
1944             elif self
._autoformat
.find('DMMMY') != -1: self
._datestyle 
= 'DMY' 
1946         # Give derived controls a chance to react to parameter changes before 
1947         # potentially changing current value of the control. 
1948         self
._OnCtrlParametersChanged
() 
1950         if self
.controlInitialized
: 
1951             # Then the base control is available for configuration; 
1952             # take action on base control based on new settings, as appropriate. 
1953             if kwargs
.has_key('useFixedWidthFont'): 
1954                 # Set control font - fixed width by default 
1957             if reset_args
.has_key('reset_mask'): 
1958 ##                dbg('reset mask') 
1959                 curvalue 
= self
._GetValue
() 
1960                 if curvalue
.strip(): 
1962 ##                        dbg('attempting to _SetInitialValue(%s)' % self._GetValue()) 
1963                         self
._SetInitialValue
(self
._GetValue
()) 
1964                     except Exception, e
: 
1965 ##                        dbg('exception caught:', e) 
1966 ##                        dbg("current value doesn't work; attempting to reset to template") 
1967                         self
._SetInitialValue
() 
1969 ##                    dbg('attempting to _SetInitialValue() with template') 
1970                     self
._SetInitialValue
() 
1972             elif kwargs
.has_key('useParensForNegatives'): 
1973                 newvalue 
= self
._getSignedValue
()[0] 
1975                 if newvalue 
is not None: 
1976                     # Adjust for new mask: 
1977                     if len(newvalue
) < len(self
._mask
): 
1979                     elif len(newvalue
) > len(self
._mask
): 
1980                         if newvalue
[-1] in (' ', ')'): 
1981                             newvalue 
= newvalue
[:-1] 
1983 ##                    dbg('reconfiguring value for parens:"%s"' % newvalue) 
1984                     self
._SetValue
(newvalue
) 
1986                     if self
._prevValue 
!= newvalue
: 
1987                         self
._prevValue 
= newvalue  
# disallow undo of sign type 
1990 ##                dbg('calculated size:', self._CalcSize()) 
1991                 self
.SetClientSize(self
._CalcSize
()) 
1992                 width 
= self
.GetSize().width
 
1993                 height 
= self
.GetBestSize().height
 
1994 ##                dbg('setting client size to:', (width, height)) 
1995                 self
.SetBestFittingSize((width
, height
)) 
1997             # Set value/type-specific formatting 
1998             self
._applyFormatting
() 
1999 ##        dbg(indent=0, suspend=0) 
2001     def SetMaskParameters(self
, **kwargs
): 
2002         """ old name for the SetCtrlParameters function  (DEPRECATED)""" 
2003         return self
.SetCtrlParameters(**kwargs
) 
2006     def GetCtrlParameter(self
, paramname
): 
2008         Routine for retrieving the value of any given parameter 
2010         if MaskedEditMixin
.valid_ctrl_params
.has_key(paramname
.replace('Color','Colour')): 
2011             return getattr(self
, '_' + paramname
.replace('Color', 'Colour')) 
2012         elif Field
.valid_params
.has_key(paramname
): 
2013             return self
._ctrl
_constraints
._GetParameter
(paramname
) 
2015             TypeError('"%s".GetCtrlParameter: invalid parameter "%s"' % (self
.name
, paramname
)) 
2017     def GetMaskParameter(self
, paramname
): 
2018         """ old name for the GetCtrlParameters function  (DEPRECATED)""" 
2019         return self
.GetCtrlParameter(paramname
) 
2022 ## This idea worked, but Boa was unable to use this solution... 
2023 ##    def _attachMethod(self, func): 
2025 ##        setattr(self, func.__name__, new.instancemethod(func, self, self.__class__)) 
2028 ##    def _DefinePropertyFunctions(exposed_params): 
2029 ##        for param in exposed_params: 
2030 ##            propname = param[0].upper() + param[1:] 
2032 ##            exec('def Set%s(self, value): self.SetCtrlParameters(%s=value)' % (propname, param)) 
2033 ##            exec('def Get%s(self): return self.GetCtrlParameter("%s")''' % (propname, param)) 
2034 ##            self._attachMethod(locals()['Set%s' % propname]) 
2035 ##            self._attachMethod(locals()['Get%s' % propname]) 
2037 ##            if param.find('Colour') != -1: 
2038 ##                # add non-british spellings, for backward-compatibility 
2039 ##                propname.replace('Colour', 'Color') 
2041 ##                exec('def Set%s(self, value): self.SetCtrlParameters(%s=value)' % (propname, param)) 
2042 ##                exec('def Get%s(self): return self.GetCtrlParameter("%s")''' % (propname, param)) 
2043 ##                self._attachMethod(locals()['Set%s' % propname]) 
2044 ##                self._attachMethod(locals()['Get%s' % propname]) 
2048     def SetFieldParameters(self
, field_index
, **kwargs
): 
2050         Routine provided to modify the parameters of a given field. 
2051         Because changes to fields can affect the overall control, 
2052         direct access to the fields is prevented, and the control 
2053         is always "reconfigured" after setting a field parameter. 
2054         (See maskededit module overview for the list of valid field-level 
2057         if field_index 
not in self
._field
_indices
: 
2058             raise IndexError('%s is not a valid field for control "%s".' % (str(field_index
), self
.name
)) 
2059         # set parameters as requested: 
2060         self
._fields
[field_index
]._SetParameters
(**kwargs
) 
2062         # Possibly reprogram control template due to resulting changes, and ensure 
2063         # control-level params are still propagated to fields: 
2064         self
._configure
(self
._previous
_mask
) 
2065         self
._fields
[field_index
]._ValidateParameters
(**kwargs
) 
2067         if self
.controlInitialized
: 
2068             if kwargs
.has_key('fillChar') or kwargs
.has_key('defaultValue'): 
2069                 self
._SetInitialValue
() 
2072                     # this is tricky, because, as Robin explains: 
2073                     # "Basically there are two sizes to deal with, that are potentially  
2074                     #  different.  The client size is the inside size and may, depending 
2075                     #  on platform, exclude the borders and such.  The normal size is 
2076                     #  the outside size that does include the borders.  What you are 
2077                     #  calculating (in _CalcSize) is the client size, but the sizers 
2078                     #  deal with the full size and so that is the minimum size that 
2079                     #  we need to set with SetBestFittingSize.  The root of the problem is 
2080                     #  that in _calcSize the current client size height is returned, 
2081                     #  instead of a height based on the current font.  So I suggest using 
2082                     #  _calcSize to just get the width, and then use GetBestSize to 
2084                     self
.SetClientSize(self
._CalcSize
()) 
2085                     width 
= self
.GetSize().width
 
2086                     height 
= self
.GetBestSize().height
 
2087                     self
.SetBestFittingSize((width
, height
)) 
2090             # Set value/type-specific formatting 
2091             self
._applyFormatting
() 
2094     def GetFieldParameter(self
, field_index
, paramname
): 
2096         Routine provided for getting a parameter of an individual field. 
2098         if field_index 
not in self
._field
_indices
: 
2099             raise IndexError('%s is not a valid field for control "%s".' % (str(field_index
), self
.name
)) 
2100         elif Field
.valid_params
.has_key(paramname
): 
2101             return self
._fields
[field_index
]._GetParameter
(paramname
) 
2103             TypeError('"%s".GetFieldParameter: invalid parameter "%s"' % (self
.name
, paramname
)) 
2106     def _SetKeycodeHandler(self
, keycode
, func
): 
2108         This function adds and/or replaces key event handling functions 
2109         used by the control.  <func> should take the event as argument 
2110         and return False if no further action on the key is necessary. 
2113             self
._keyhandlers
[keycode
] = func
 
2114         elif self
._keyhandlers
.has_key(keycode
): 
2115             del self
._keyhandlers
[keycode
] 
2118     def _SetKeyHandler(self
, char
, func
): 
2120         This function adds and/or replaces key event handling functions 
2121         for ascii characters.  <func> should take the event as argument 
2122         and return False if no further action on the key is necessary. 
2124         self
._SetKeycodeHandler
(ord(char
), func
) 
2127     def _AddNavKeycode(self
, keycode
, handler
=None): 
2129         This function allows a derived subclass to augment the list of 
2130         keycodes that are considered "navigational" keys. 
2132         self
._nav
.append(keycode
) 
2134             self
._keyhandlers
[keycode
] = handler
 
2135         elif self
.keyhandlers
.has_key(keycode
): 
2136             del self
._keyhandlers
[keycode
] 
2140     def _AddNavKey(self
, char
, handler
=None): 
2142         This function is a convenience function so you don't have to 
2143         remember to call ord() for ascii chars to be used for navigation. 
2145         self
._AddNavKeycode
(ord(char
), handler
) 
2148     def _GetNavKeycodes(self
): 
2150         This function retrieves the current list of navigational keycodes for 
2156     def _SetNavKeycodes(self
, keycode_func_tuples
): 
2158         This function allows you to replace the current list of keycode processed 
2159         as navigation keys, and bind associated optional keyhandlers. 
2162         for keycode
, func 
in keycode_func_tuples
: 
2163             self
._nav
.append(keycode
) 
2165                 self
._keyhandlers
[keycode
] = func
 
2166             elif self
.keyhandlers
.has_key(keycode
): 
2167                 del self
._keyhandlers
[keycode
] 
2170     def _processMask(self
, mask
): 
2172         This subroutine expands {n} syntax in mask strings, and looks for escaped 
2173         special characters and returns the expanded mask, and an dictionary 
2174         of booleans indicating whether or not a given position in the mask is 
2175         a mask character or not. 
2177 ##        dbg('_processMask: mask', mask, indent=1) 
2178         # regular expression for parsing c{n} syntax: 
2179         rex 
= re
.compile('([' +string
.join(maskchars
,"") + '])\{(\d+)\}') 
2181         match 
= rex
.search(s
) 
2182         while match
:    # found an(other) occurrence 
2183             maskchr 
= s
[match
.start(1):match
.end(1)]            # char to be repeated 
2184             repcount 
= int(s
[match
.start(2):match
.end(2)])      # the number of times 
2185             replacement 
= string
.join( maskchr 
* repcount
, "")  # the resulting substr 
2186             s 
= s
[:match
.start(1)] + replacement 
+ s
[match
.end(2)+1:]   #account for trailing '}' 
2187             match 
= rex
.search(s
)                               # look for another such entry in mask 
2189         self
._decimalChar 
= self
._ctrl
_constraints
._decimalChar
 
2190         self
._shiftDecimalChar 
= self
._ctrl
_constraints
._shiftDecimalChar
 
2192         self
._isFloat      
= _isFloatingPoint(s
) and not self
._ctrl
_constraints
._validRegex
 
2193         self
._isInt      
= _isInteger(s
) and not self
._ctrl
_constraints
._validRegex
 
2194         self
._signOk     
= '-' in self
._ctrl
_constraints
._formatcodes 
and (self
._isFloat 
or self
._isInt
) 
2195         self
._useParens  
= self
._ctrl
_constraints
._useParensForNegatives
 
2197 ####        dbg('self._signOk?', self._signOk, 'self._useParens?', self._useParens) 
2198 ####        dbg('isFloatingPoint(%s)?' % (s), _isFloatingPoint(s), 
2199 ##            'ctrl regex:', self._ctrl_constraints._validRegex) 
2201         if self
._signOk 
and s
[0] != ' ': 
2203             if self
._ctrl
_constraints
._defaultValue 
and self
._ctrl
_constraints
._defaultValue
[0] != ' ': 
2204                 self
._ctrl
_constraints
._defaultValue 
= ' ' + self
._ctrl
_constraints
._defaultValue
 
2209                 self
._ctrl
_constraints
._defaultValue 
+= ' ' 
2211         # Now, go build up a dictionary of booleans, indexed by position, 
2212         # indicating whether or not a given position is masked or not 
2216             if s
[i
] == '\\':            # if escaped character: 
2217                 ismasked
[i
] = False     #     mark position as not a mask char 
2218                 if i
+1 < len(s
):        #     if another char follows... 
2219                     s 
= s
[:i
] + s
[i
+1:] #         elide the '\' 
2220                     if i
+2 < len(s
) and s
[i
+1] == '\\': 
2221                         # if next char also a '\', char is a literal '\' 
2222                         s 
= s
[:i
] + s
[i
+1:]     # elide the 2nd '\' as well 
2223             else:                       # else if special char, mark position accordingly 
2224                 ismasked
[i
] = s
[i
] in maskchars
 
2225 ####            dbg('ismasked[%d]:' % i, ismasked[i], s) 
2226             i 
+= 1                      # increment to next char 
2227 ####        dbg('ismasked:', ismasked) 
2228 ##        dbg('new mask: "%s"' % s, indent=0) 
2233     def _calcFieldExtents(self
): 
2235         Subroutine responsible for establishing/configuring field instances with 
2236         indices and editable extents appropriate to the specified mask, and building 
2237         the lookup table mapping each position to the corresponding field. 
2239         self
._lookupField 
= {} 
2242             ## Create dictionary of positions,characters in mask 
2244             for charnum 
in range( len( self
._mask
)): 
2245                 self
.maskdict
[charnum
] = self
._mask
[charnum
:charnum
+1] 
2247             # For the current mask, create an ordered list of field extents 
2248             # and a dictionary of positions that map to field indices: 
2250             if self
._signOk
: start 
= 1 
2254                 # Skip field "discovery", and just construct a 2-field control with appropriate 
2255                 # constraints for a floating-point entry. 
2257                 # .setdefault always constructs 2nd argument even if not needed, so we do this 
2258                 # the old-fashioned way... 
2259                 if not self
._fields
.has_key(0): 
2260                     self
._fields
[0] = Field() 
2261                 if not self
._fields
.has_key(1): 
2262                     self
._fields
[1] = Field() 
2264                 self
._decimalpos 
= string
.find( self
._mask
, '.') 
2265 ##                dbg('decimal pos =', self._decimalpos) 
2267                 formatcodes 
= self
._fields
[0]._GetParameter
('formatcodes') 
2268                 if 'R' not in formatcodes
: formatcodes 
+= 'R' 
2269                 self
._fields
[0]._SetParameters
(index
=0, extent
=(start
, self
._decimalpos
), 
2270                                                mask
=self
._mask
[start
:self
._decimalpos
], formatcodes
=formatcodes
) 
2271                 end 
= len(self
._mask
) 
2272                 if self
._signOk 
and self
._useParens
: 
2274                 self
._fields
[1]._SetParameters
(index
=1, extent
=(self
._decimalpos
+1, end
), 
2275                                                mask
=self
._mask
[self
._decimalpos
+1:end
]) 
2277                 for i 
in range(self
._decimalpos
+1): 
2278                     self
._lookupField
[i
] = 0 
2280                 for i 
in range(self
._decimalpos
+1, len(self
._mask
)+1): 
2281                     self
._lookupField
[i
] = 1 
2284                 # Skip field "discovery", and just construct a 1-field control with appropriate 
2285                 # constraints for a integer entry. 
2286                 if not self
._fields
.has_key(0): 
2287                     self
._fields
[0] = Field(index
=0) 
2288                 end 
= len(self
._mask
) 
2289                 if self
._signOk 
and self
._useParens
: 
2291                 self
._fields
[0]._SetParameters
(index
=0, extent
=(start
, end
), 
2292                                                mask
=self
._mask
[start
:end
]) 
2293                 for i 
in range(len(self
._mask
)+1): 
2294                     self
._lookupField
[i
] = 0 
2296                 # generic control; parse mask to figure out where the fields are: 
2299                 i 
= self
._findNextEntry
(pos
,adjustInsert
=False)  # go to 1st entry point: 
2300                 if i 
< len(self
._mask
):   # no editable chars! 
2301                     for j 
in range(pos
, i
+1): 
2302                         self
._lookupField
[j
] = field_index
 
2303                     pos 
= i       
# figure out field for 1st editable space: 
2305                 while i 
<= len(self
._mask
): 
2306 ####                    dbg('searching: outer field loop: i = ', i) 
2307                     if self
._isMaskChar
(i
): 
2308 ####                        dbg('1st char is mask char; recording edit_start=', i) 
2310                         # Skip to end of editable part of current field: 
2311                         while i 
< len(self
._mask
) and self
._isMaskChar
(i
): 
2312                             self
._lookupField
[i
] = field_index
 
2314 ####                        dbg('edit_end =', i) 
2316                         self
._lookupField
[i
] = field_index
 
2317 ####                        dbg('self._fields.has_key(%d)?' % field_index, self._fields.has_key(field_index)) 
2318                         if not self
._fields
.has_key(field_index
): 
2319                             kwargs 
= Field
.valid_params
.copy() 
2320                             kwargs
['index'] = field_index
 
2321                             kwargs
['extent'] = (edit_start
, edit_end
) 
2322                             kwargs
['mask'] = self
._mask
[edit_start
:edit_end
] 
2323                             self
._fields
[field_index
] = Field(**kwargs
) 
2325                             self
._fields
[field_index
]._SetParameters
( 
2327                                                                 extent
=(edit_start
, edit_end
), 
2328                                                                 mask
=self
._mask
[edit_start
:edit_end
]) 
2330                     i 
= self
._findNextEntry
(pos
, adjustInsert
=False)  # go to next field: 
2332                         for j 
in range(pos
, i
+1): 
2333                             self
._lookupField
[j
] = field_index
 
2334                     if i 
>= len(self
._mask
): 
2335                         break           # if past end, we're done 
2338 ####                        dbg('next field:', field_index) 
2340         indices 
= self
._fields
.keys() 
2342         self
._field
_indices 
= indices
[1:] 
2343 ####        dbg('lookupField map:', indent=1) 
2344 ##        for i in range(len(self._mask)): 
2345 ####            dbg('pos %d:' % i, self._lookupField[i]) 
2348         # Verify that all field indices specified are valid for mask: 
2349         for index 
in self
._fields
.keys(): 
2350             if index 
not in [-1] + self
._lookupField
.values(): 
2351                 raise IndexError('field %d is not a valid field for mask "%s"' % (index
, self
._mask
)) 
2354     def _calcTemplate(self
, reset_fillchar
, reset_default
): 
2356         Subroutine for processing current fillchars and default values for 
2357         whole control and individual fields, constructing the resulting 
2358         overall template, and adjusting the current value as necessary. 
2361         if self
._ctrl
_constraints
._defaultValue
: 
2364             for field 
in self
._fields
.values(): 
2365                 if field
._defaultValue 
and not reset_default
: 
2367 ##        dbg('default set?', default_set) 
2369         # Determine overall new template for control, and keep track of previous 
2370         # values, so that current control value can be modified as appropriate: 
2371         if self
.controlInitialized
: curvalue 
= list(self
._GetValue
()) 
2372         else:                       curvalue 
= None 
2374         if hasattr(self
, '_fillChar'): old_fillchars 
= self
._fillChar
 
2375         else:                          old_fillchars 
= None 
2377         if hasattr(self
, '_template'): old_template 
= self
._template
 
2378         else:                          old_template 
= None 
2385         for field 
in self
._fields
.values(): 
2386             field
._template 
= "" 
2388         for pos 
in range(len(self
._mask
)): 
2389 ####            dbg('pos:', pos) 
2390             field 
= self
._FindField
(pos
) 
2391 ####            dbg('field:', field._index) 
2392             start
, end 
= field
._extent
 
2394             if pos 
== 0 and self
._signOk
: 
2395                 self
._template 
= ' ' # always make 1st 1st position blank, regardless of fillchar 
2396             elif self
._isFloat 
and pos 
== self
._decimalpos
: 
2397                 self
._template 
+= self
._decimalChar
 
2398             elif self
._isMaskChar
(pos
): 
2399                 if field
._fillChar 
!= self
._ctrl
_constraints
._fillChar 
and not reset_fillchar
: 
2400                     fillChar 
= field
._fillChar
 
2402                     fillChar 
= self
._ctrl
_constraints
._fillChar
 
2403                 self
._fillChar
[pos
] = fillChar
 
2405                 # Replace any current old fillchar with new one in current value; 
2406                 # if action required, set reset_value flag so we can take that action 
2407                 # after we're all done 
2408                 if self
.controlInitialized 
and old_fillchars 
and old_fillchars
.has_key(pos
) and curvalue
: 
2409                     if curvalue
[pos
] == old_fillchars
[pos
] and old_fillchars
[pos
] != fillChar
: 
2411                         curvalue
[pos
] = fillChar
 
2413                 if not field
._defaultValue 
and not self
._ctrl
_constraints
._defaultValue
: 
2414 ####                    dbg('no default value') 
2415                     self
._template 
+= fillChar
 
2416                     field
._template 
+= fillChar
 
2418                 elif field
._defaultValue 
and not reset_default
: 
2419 ####                    dbg('len(field._defaultValue):', len(field._defaultValue)) 
2420 ####                    dbg('pos-start:', pos-start) 
2421                     if len(field
._defaultValue
) > pos
-start
: 
2422 ####                        dbg('field._defaultValue[pos-start]: "%s"' % field._defaultValue[pos-start]) 
2423                         self
._template 
+= field
._defaultValue
[pos
-start
] 
2424                         field
._template 
+= field
._defaultValue
[pos
-start
] 
2426 ####                        dbg('field default not long enough; using fillChar') 
2427                         self
._template 
+= fillChar
 
2428                         field
._template 
+= fillChar
 
2430                     if len(self
._ctrl
_constraints
._defaultValue
) > pos
: 
2431 ####                        dbg('using control default') 
2432                         self
._template 
+= self
._ctrl
_constraints
._defaultValue
[pos
] 
2433                         field
._template 
+= self
._ctrl
_constraints
._defaultValue
[pos
] 
2435 ####                        dbg('ctrl default not long enough; using fillChar') 
2436                         self
._template 
+= fillChar
 
2437                         field
._template 
+= fillChar
 
2438 ####                dbg('field[%d]._template now "%s"' % (field._index, field._template)) 
2439 ####                dbg('self._template now "%s"' % self._template) 
2441                 self
._template 
+= self
._mask
[pos
] 
2443         self
._fields
[-1]._template 
= self
._template     
# (for consistency) 
2445         if curvalue
:    # had an old value, put new one back together 
2446             newvalue 
= string
.join(curvalue
, "") 
2451             self
._defaultValue 
= self
._template
 
2452 ##            dbg('self._defaultValue:', self._defaultValue) 
2453             if not self
.IsEmpty(self
._defaultValue
) and not self
.IsValid(self
._defaultValue
): 
2455                 raise ValueError('Default value of "%s" is not a valid value for control "%s"' % (self
._defaultValue
, self
.name
)) 
2457             # if no fillchar change, but old value == old template, replace it: 
2458             if newvalue 
== old_template
: 
2459                 newvalue 
= self
._template
 
2462             self
._defaultValue 
= None 
2465 ##            dbg('resetting value to: "%s"' % newvalue) 
2466             pos 
= self
._GetInsertionPoint
() 
2467             sel_start
, sel_to 
= self
._GetSelection
() 
2468             self
._SetValue
(newvalue
) 
2469             self
._SetInsertionPoint
(pos
) 
2470             self
._SetSelection
(sel_start
, sel_to
) 
2473     def _propagateConstraints(self
, **reset_args
): 
2475         Subroutine for propagating changes to control-level constraints and 
2476         formatting to the individual fields as appropriate. 
2478         parent_codes 
= self
._ctrl
_constraints
._formatcodes
 
2479         parent_includes 
= self
._ctrl
_constraints
._includeChars
 
2480         parent_excludes 
= self
._ctrl
_constraints
._excludeChars
 
2481         for i 
in self
._field
_indices
: 
2482             field 
= self
._fields
[i
] 
2484             if len(self
._field
_indices
) == 1: 
2485                 inherit_args
['formatcodes'] = parent_codes
 
2486                 inherit_args
['includeChars'] = parent_includes
 
2487                 inherit_args
['excludeChars'] = parent_excludes
 
2489                 field_codes 
= current_codes 
= field
._GetParameter
('formatcodes') 
2490                 for c 
in parent_codes
: 
2491                     if c 
not in field_codes
: field_codes 
+= c
 
2492                 if field_codes 
!= current_codes
: 
2493                     inherit_args
['formatcodes'] = field_codes
 
2495                 include_chars 
= current_includes 
= field
._GetParameter
('includeChars') 
2496                 for c 
in parent_includes
: 
2497                     if not c 
in include_chars
: include_chars 
+= c
 
2498                 if include_chars 
!= current_includes
: 
2499                     inherit_args
['includeChars'] = include_chars
 
2501                 exclude_chars 
= current_excludes 
= field
._GetParameter
('excludeChars') 
2502                 for c 
in parent_excludes
: 
2503                     if not c 
in exclude_chars
: exclude_chars 
+= c
 
2504                 if exclude_chars 
!= current_excludes
: 
2505                     inherit_args
['excludeChars'] = exclude_chars
 
2507             if reset_args
.has_key('defaultValue') and reset_args
['defaultValue']: 
2508                 inherit_args
['defaultValue'] = ""   # (reset for field) 
2510             for param 
in Field
.propagating_params
: 
2511 ####                dbg('reset_args.has_key(%s)?' % param, reset_args.has_key(param)) 
2512 ####                dbg('reset_args.has_key(%(param)s) and reset_args[%(param)s]?' % locals(), reset_args.has_key(param) and reset_args[param]) 
2513                 if reset_args
.has_key(param
): 
2514                     inherit_args
[param
] = self
.GetCtrlParameter(param
) 
2515 ####                    dbg('inherit_args[%s]' % param, inherit_args[param]) 
2518                 field
._SetParameters
(**inherit_args
) 
2519                 field
._ValidateParameters
(**inherit_args
) 
2522     def _validateChoices(self
): 
2524         Subroutine that validates that all choices for given fields are at 
2525         least of the necessary length, and that they all would be valid pastes 
2526         if pasted into their respective fields. 
2528         for field 
in self
._fields
.values(): 
2530                 index 
= field
._index
 
2531                 if len(self
._field
_indices
) == 1 and index 
== 0 and field
._choices 
== self
._ctrl
_constraints
._choices
: 
2532 ##                    dbg('skipping (duplicate) choice validation of field 0') 
2534 ####                dbg('checking for choices for field', field._index) 
2535                 start
, end 
= field
._extent
 
2536                 field_length 
= end 
- start
 
2537 ####                dbg('start, end, length:', start, end, field_length) 
2538                 for choice 
in field
._choices
: 
2539 ####                    dbg('testing "%s"' % choice) 
2540                     valid_paste
, ignore
, replace_to 
= self
._validatePaste
(choice
, start
, end
) 
2543                         raise ValueError('"%s" could not be entered into field %d of control "%s"' % (choice
, index
, self
.name
)) 
2544                     elif replace_to 
> end
: 
2546                         raise ValueError('"%s" will not fit into field %d of control "%s"' (choice
, index
, self
.name
)) 
2547 ####                    dbg(choice, 'valid in field', index) 
2550     def _configure(self
, mask
, **reset_args
): 
2552         This function sets flags for automatic styling options.  It is 
2553         called whenever a control or field-level parameter is set/changed. 
2555         This routine does the bulk of the interdependent parameter processing, determining 
2556         the field extents of the mask if changed, resetting parameters as appropriate, 
2557         determining the overall template value for the control, etc. 
2559         reset_args is supplied if called from control's .SetCtrlParameters() 
2560         routine, and indicates which if any parameters which can be 
2561         overridden by individual fields have been reset by request for the 
2566 ##        dbg('MaskedEditMixin::_configure("%s")' % mask, indent=1) 
2568         # Preprocess specified mask to expand {n} syntax, handle escaped 
2569         # mask characters, etc and build the resulting positionally keyed 
2570         # dictionary for which positions are mask vs. template characters: 
2571         self
._mask
, self
.ismasked 
= self
._processMask
(mask
) 
2572         self
._masklength 
= len(self
._mask
) 
2573 ####        dbg('processed mask:', self._mask) 
2575         # Preserve original mask specified, for subsequent reprocessing 
2576         # if parameters change. 
2577 ##        dbg('mask: "%s"' % self._mask, 'previous mask: "%s"' % self._previous_mask) 
2578         self
._previous
_mask 
= mask    
# save unexpanded mask for next time 
2579             # Set expanded mask and extent of field -1 to width of entire control: 
2580         self
._ctrl
_constraints
._SetParameters
(mask 
= self
._mask
, extent
=(0,self
._masklength
)) 
2582         # Go parse mask to determine where each field is, construct field 
2583         # instances as necessary, configure them with those extents, and 
2584         # build lookup table mapping each position for control to its corresponding 
2586 ####        dbg('calculating field extents') 
2588         self
._calcFieldExtents
() 
2591         # Go process defaultValues and fillchars to construct the overall 
2592         # template, and adjust the current value as necessary: 
2593         reset_fillchar 
= reset_args
.has_key('fillChar') and reset_args
['fillChar'] 
2594         reset_default 
= reset_args
.has_key('defaultValue') and reset_args
['defaultValue'] 
2596 ####        dbg('calculating template') 
2597         self
._calcTemplate
(reset_fillchar
, reset_default
) 
2599         # Propagate control-level formatting and character constraints to each 
2600         # field if they don't already have them; if only one field, propagate 
2601         # control-level validation constraints to field as well: 
2602 ####        dbg('propagating constraints') 
2603         self
._propagateConstraints
(**reset_args
) 
2606         if self
._isFloat 
and self
._fields
[0]._groupChar 
== self
._decimalChar
: 
2607             raise AttributeError('groupChar (%s) and decimalChar (%s) must be distinct.' % 
2608                                  (self
._fields
[0]._groupChar
, self
._decimalChar
) ) 
2610 ####        dbg('fields:', indent=1) 
2611 ##        for i in [-1] + self._field_indices: 
2612 ####            dbg('field %d:' % i, self._fields[i].__dict__) 
2615         # Set up special parameters for numeric control, if appropriate: 
2617             self
._signpos 
= 0   # assume it starts here, but it will move around on floats 
2618             signkeys 
= ['-', '+', ' '] 
2620                 signkeys 
+= ['(', ')'] 
2621             for key 
in signkeys
: 
2623                 if not self
._keyhandlers
.has_key(keycode
): 
2624                     self
._SetKeyHandler
(key
, self
._OnChangeSign
) 
2625         elif self
._isInt 
or self
._isFloat
: 
2626             signkeys 
= ['-', '+', ' ', '(', ')'] 
2627             for key 
in signkeys
: 
2629                 if self
._keyhandlers
.has_key(keycode
) and self
._keyhandlers
[keycode
] == self
._OnChangeSign
: 
2630                     self
._SetKeyHandler
(key
, None) 
2634         if self
._isFloat 
or self
._isInt
: 
2635             if self
.controlInitialized
: 
2636                 value 
= self
._GetValue
() 
2637 ####                dbg('value: "%s"' % value, 'len(value):', len(value), 
2638 ##                    'len(self._ctrl_constraints._mask):',len(self._ctrl_constraints._mask)) 
2639                 if len(value
) < len(self
._ctrl
_constraints
._mask
): 
2641                     if self
._useParens 
and len(newvalue
) < len(self
._ctrl
_constraints
._mask
) and newvalue
.find('(') == -1: 
2643                     if self
._signOk 
and len(newvalue
) < len(self
._ctrl
_constraints
._mask
) and newvalue
.find(')') == -1: 
2644                         newvalue 
= ' ' + newvalue
 
2645                     if len(newvalue
) < len(self
._ctrl
_constraints
._mask
): 
2646                         if self
._ctrl
_constraints
._alignRight
: 
2647                             newvalue 
= newvalue
.rjust(len(self
._ctrl
_constraints
._mask
)) 
2649                             newvalue 
= newvalue
.ljust(len(self
._ctrl
_constraints
._mask
)) 
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
() 
2658                 elif len(value
) > len(self
._ctrl
_constraints
._mask
): 
2660                     if not self
._useParens 
and newvalue
[-1] == ' ': 
2661                         newvalue 
= newvalue
[:-1] 
2662                     if not self
._signOk 
and len(newvalue
) > len(self
._ctrl
_constraints
._mask
): 
2663                         newvalue 
= newvalue
[1:] 
2664                     if not self
._signOk
: 
2665                         newvalue
, signpos
, right_signpos 
= self
._getSignedValue
(newvalue
) 
2667 ##                    dbg('old value: "%s"' % value) 
2668 ##                    dbg('new value: "%s"' % newvalue) 
2670                         self
._SetValue
(newvalue
) 
2671                     except Exception, e
: 
2672 ##                        dbg('exception raised:', e, 'resetting to initial value') 
2673                         self
._SetInitialValue
() 
2674                 elif not self
._signOk 
and ('(' in value 
or '-' in value
): 
2675                     newvalue
, signpos
, right_signpos 
= self
._getSignedValue
(value
) 
2676 ##                    dbg('old value: "%s"' % value) 
2677 ##                    dbg('new value: "%s"' % newvalue) 
2679                         self
._SetValue
(newvalue
) 
2681 ##                        dbg('exception raised:', e, 'resetting to initial value') 
2682                         self
._SetInitialValue
() 
2684             # Replace up/down arrow default handling: 
2685             # make down act like tab, up act like shift-tab: 
2687 ####            dbg('Registering numeric navigation and control handlers (if not already set)') 
2688             if not self
._keyhandlers
.has_key(wx
.WXK_DOWN
): 
2689                 self
._SetKeycodeHandler
(wx
.WXK_DOWN
, self
._OnChangeField
) 
2690             if not self
._keyhandlers
.has_key(wx
.WXK_UP
): 
2691                 self
._SetKeycodeHandler
(wx
.WXK_UP
, self
._OnUpNumeric
)  # (adds "shift" to up arrow, and calls _OnChangeField) 
2693             # On ., truncate contents right of cursor to decimal point (if any) 
2694             # leaves cursor after decimal point if floating point, otherwise at 0. 
2695             if not self
._keyhandlers
.has_key(ord(self
._decimalChar
)) or self
._keyhandlers
[ord(self
._decimalChar
)] != self
._OnDecimalPoint
: 
2696                 self
._SetKeyHandler
(self
._decimalChar
, self
._OnDecimalPoint
) 
2698             if not self
._keyhandlers
.has_key(ord(self
._shiftDecimalChar
)) or self
._keyhandlers
[ord(self
._shiftDecimalChar
)] != self
._OnChangeField
: 
2699                 self
._SetKeyHandler
(self
._shiftDecimalChar
, self
._OnChangeField
)   # (Shift-'.' == '>' on US keyboards) 
2701             # Allow selective insert of groupchar in numbers: 
2702             if not self
._keyhandlers
.has_key(ord(self
._fields
[0]._groupChar
)) or self
._keyhandlers
[ord(self
._fields
[0]._groupChar
)] != self
._OnGroupChar
: 
2703                 self
._SetKeyHandler
(self
._fields
[0]._groupChar
, self
._OnGroupChar
) 
2705 ##        dbg(indent=0, suspend=0) 
2708     def _SetInitialValue(self
, value
=""): 
2710         fills the control with the generated or supplied default value. 
2711         It will also set/reset the font if necessary and apply 
2712         formatting to the control at this time. 
2714 ##        dbg('MaskedEditMixin::_SetInitialValue("%s")' % value, indent=1) 
2716             self
._prevValue 
= self
._curValue 
= self
._template
 
2717             # don't apply external validation rules in this case, as template may 
2718             # not coincide with "legal" value... 
2720                 self
._SetValue
(self
._curValue
)  # note the use of "raw" ._SetValue()... 
2721             except Exception, e
: 
2722 ##                dbg('exception thrown:', e, indent=0) 
2725             # Otherwise apply validation as appropriate to passed value: 
2726 ####            dbg('value = "%s", length:' % value, len(value)) 
2727             self
._prevValue 
= self
._curValue 
= value
 
2729                 self
.SetValue(value
)            # use public (validating) .SetValue() 
2730             except Exception, e
: 
2731 ##                dbg('exception thrown:', e, indent=0) 
2735         # Set value/type-specific formatting 
2736         self
._applyFormatting
() 
2740     def _calcSize(self
, size
=None): 
2741         """ Calculate automatic size if allowed; must be called after the base control is instantiated""" 
2742 ####        dbg('MaskedEditMixin::_calcSize', indent=1) 
2743         cont 
= (size 
is None or size 
== wx
.DefaultSize
) 
2745         if cont 
and self
._autofit
: 
2746             sizing_text 
= 'M' * self
._masklength
 
2747             if wx
.Platform 
!= "__WXMSW__":   # give it a little extra space 
2749             if wx
.Platform 
== "__WXMAC__":   # give it even a little more... 
2751 ####            dbg('len(sizing_text):', len(sizing_text), 'sizing_text: "%s"' % sizing_text) 
2752             w
, h 
= self
.GetTextExtent(sizing_text
) 
2753             size 
= (w
+4, self
.GetSize().height
) 
2754 ####            dbg('size:', size, indent=0) 
2759         """ Set the control's font typeface -- pass the font name as str.""" 
2760 ####        dbg('MaskedEditMixin::_setFont', indent=1) 
2761         if not self
._useFixedWidthFont
: 
2762             self
._font 
= wx
.SystemSettings_GetFont(wx
.SYS_DEFAULT_GUI_FONT
) 
2764             font 
= self
.GetFont()   # get size, weight, etc from current font 
2766             # Set to teletype font (guaranteed to be mappable to all wxWindows 
2768             self
._font 
= wx
.Font( font
.GetPointSize(), wx
.TELETYPE
, font
.GetStyle(), 
2769                                  font
.GetWeight(), font
.GetUnderlined()) 
2770 ####            dbg('font string: "%s"' % font.GetNativeFontInfo().ToString()) 
2772         self
.SetFont(self
._font
) 
2776     def _OnTextChange(self
, event
): 
2778         Handler for EVT_TEXT event. 
2779         self._Change() is provided for subclasses, and may return False to 
2780         skip this method logic.  This function returns True if the event 
2781         detected was a legitimate event, or False if it was a "bogus" 
2782         EVT_TEXT event.  (NOTE: There is currently an issue with calling 
2783         .SetValue from within the EVT_CHAR handler that causes duplicate 
2784         EVT_TEXT events for the same change.) 
2786         newvalue 
= self
._GetValue
() 
2787 ##        dbg('MaskedEditMixin::_OnTextChange: value: "%s"' % newvalue, indent=1) 
2789         if self
._ignoreChange
:      # ie. if an "intermediate text change event" 
2793         ##! WS: For some inexplicable reason, every wx.TextCtrl.SetValue 
2794         ## call is generating two (2) EVT_TEXT events.  On certain platforms, 
2795         ## (eg. linux/GTK) the 1st is an empty string value. 
2796         ## This is the only mechanism I can find to mask this problem: 
2797         if newvalue 
== self
._curValue 
or len(newvalue
) == 0: 
2798 ##            dbg('ignoring bogus text change event', indent=0) 
2801 ##            dbg('curvalue: "%s", newvalue: "%s", len(newvalue): %d' % (self._curValue, newvalue, len(newvalue))) 
2803                 if self
._signOk 
and self
._isNeg 
and newvalue
.find('-') == -1 and newvalue
.find('(') == -1: 
2804 ##                    dbg('clearing self._isNeg') 
2806                     text
, self
._signpos
, self
._right
_signpos 
= self
._getSignedValue
() 
2807                 self
._CheckValid
()  # Recolor control as appropriate 
2808 ##            dbg('calling event.Skip()') 
2811         self
._prevValue 
= self
._curValue    
# save for undo 
2812         self
._curValue 
= newvalue           
# Save last seen value for next iteration 
2817     def _OnKeyDown(self
, event
): 
2819         This function allows the control to capture Ctrl-events like Ctrl-tab, 
2820         that are not normally seen by the "cooked" EVT_CHAR routine. 
2822         # Get keypress value, adjusted by control options (e.g. convert to upper etc) 
2823         key    
= event
.GetKeyCode() 
2824         if key 
in self
._nav 
and event
.ControlDown(): 
2825             # then this is the only place we will likely see these events; 
2827 ##            dbg('MaskedEditMixin::OnKeyDown: calling _OnChar') 
2830         # else allow regular EVT_CHAR key processing 
2834     def _OnChar(self
, event
): 
2836         This is the engine of MaskedEdit controls.  It examines each keystroke, 
2837         decides if it's allowed, where it should go or what action to take. 
2839 ##        dbg('MaskedEditMixin::_OnChar', indent=1) 
2841         # Get keypress value, adjusted by control options (e.g. convert to upper etc) 
2842         key 
= event
.GetKeyCode() 
2843         orig_pos 
= self
._GetInsertionPoint
() 
2844         orig_value 
= self
._GetValue
() 
2845 ##        dbg('keycode = ', key) 
2846 ##        dbg('current pos = ', orig_pos) 
2847 ##        dbg('current selection = ', self._GetSelection()) 
2849         if not self
._Keypress
(key
): 
2853         # If no format string for this control, or the control is marked as "read-only", 
2854         # skip the rest of the special processing, and just "do the standard thing:" 
2855         if not self
._mask 
or not self
._IsEditable
(): 
2860         # Process navigation and control keys first, with 
2861         # position/selection unadulterated: 
2862         if key 
in self
._nav 
+ self
._control
: 
2863             if self
._keyhandlers
.has_key(key
): 
2864                 keep_processing 
= self
._keyhandlers
[key
](event
) 
2865                 if self
._GetValue
() != orig_value
: 
2866                     self
.modified 
= True 
2867                 if not keep_processing
: 
2870                 self
._applyFormatting
() 
2874         # Else... adjust the position as necessary for next input key, 
2875         # and determine resulting selection: 
2876         pos 
= self
._adjustPos
( orig_pos
, key 
)    ## get insertion position, adjusted as needed 
2877         sel_start
, sel_to 
= self
._GetSelection
()                ## check for a range of selected text 
2878 ##        dbg("pos, sel_start, sel_to:", pos, sel_start, sel_to) 
2880         keep_processing 
= True 
2881         # Capture user past end of format field 
2882         if pos 
> len(self
.maskdict
): 
2883 ##            dbg("field length exceeded:",pos) 
2884             keep_processing 
= False 
2887             if self
._isMaskChar
(pos
):  ## Get string of allowed characters for validation 
2888                 okchars 
= self
._getAllowedChars
(pos
) 
2890 ##                dbg('Not a valid position: pos = ', pos,"chars=",maskchars) 
2893         key 
= self
._adjustKey
(pos
, key
)     # apply formatting constraints to key: 
2895         if self
._keyhandlers
.has_key(key
): 
2896             # there's an override for default behavior; use override function instead 
2897 ##            dbg('using supplied key handler:', self._keyhandlers[key]) 
2898             keep_processing 
= self
._keyhandlers
[key
](event
) 
2899             if self
._GetValue
() != orig_value
: 
2900                 self
.modified 
= True 
2901             if not keep_processing
: 
2904             # else skip default processing, but do final formatting 
2905         if key 
< wx
.WXK_SPACE 
or key 
> 255: 
2906 ##            dbg('key < WXK_SPACE or key > 255') 
2907             event
.Skip()                # non alphanumeric 
2908             keep_processing 
= False 
2910             field 
= self
._FindField
(pos
) 
2912 ##            dbg("key ='%s'" % chr(key)) 
2914 ##                dbg('okSpaces?', field._okSpaces) 
2917             char 
= chr(key
) # (must work if we got this far) 
2919             if 'unicode' in wx
.PlatformInfo
: 
2920                 char 
= char
.decode(self
._defaultEncoding
) 
2922                 if type(field
._excludeChars
) != types
.UnicodeType
: 
2923                     excludes 
+= field
._excludeChars
.decode(self
._defaultEncoding
) 
2924                 if type(self
._ctrl
_constraints
) != types
.UnicodeType
: 
2925                     excludes 
+= self
._ctrl
_constraints
._excludeChars
.decode(self
._defaultEncoding
) 
2927                 excludes 
= field
._excludeChars 
+ self
._ctrl
_constraints
._excludeChars
 
2929             if char 
in excludes
: 
2930                 keep_processing 
= False 
2932             if keep_processing 
and self
._isCharAllowed
( char
, pos
, checkRegex 
= True ): 
2933 ##                dbg("key allowed by mask") 
2934                 # insert key into candidate new value, but don't change control yet: 
2935                 oldstr 
= self
._GetValue
() 
2936                 newstr
, newpos
, new_select_to
, match_field
, match_index 
= self
._insertKey
( 
2937                                 char
, pos
, sel_start
, sel_to
, self
._GetValue
(), allowAutoSelect 
= True) 
2938 ##                dbg("str with '%s' inserted:" % char, '"%s"' % newstr) 
2939                 if self
._ctrl
_constraints
._validRequired 
and not self
.IsValid(newstr
): 
2940 ##                    dbg('not valid; checking to see if adjusted string is:') 
2941                     keep_processing 
= False 
2942                     if self
._isFloat 
and newstr 
!= self
._template
: 
2943                         newstr 
= self
._adjustFloat
(newstr
) 
2944 ##                        dbg('adjusted str:', newstr) 
2945                         if self
.IsValid(newstr
): 
2947                             keep_processing 
= True 
2948                             wx
.CallAfter(self
._SetInsertionPoint
, self
._decimalpos
) 
2949                     if not keep_processing
: 
2950 ##                        dbg("key disallowed by validation") 
2951                         if not wx
.Validator_IsSilent() and orig_pos 
== pos
: 
2957                     # special case: adjust date value as necessary: 
2958                     if self
._isDate 
and newstr 
!= self
._template
: 
2959                         newstr 
= self
._adjustDate
(newstr
) 
2960 ##                    dbg('adjusted newstr:', newstr) 
2962                     if newstr 
!= orig_value
: 
2963                         self
.modified 
= True 
2965                     wx
.CallAfter(self
._SetValue
, newstr
) 
2967                     # Adjust insertion point on date if just entered 2 digit year, and there are now 4 digits: 
2968                     if not self
.IsDefault() and self
._isDate 
and self
._4digityear
: 
2969                         year2dig 
= self
._dateExtent 
- 2 
2970                         if pos 
== year2dig 
and unadjusted
[year2dig
] != newstr
[year2dig
]: 
2973 ##                    dbg('queuing insertion point: (%d)' % newpos) 
2974                     wx
.CallAfter(self
._SetInsertionPoint
, newpos
) 
2976                     if match_field 
is not None: 
2977 ##                        dbg('matched field') 
2978                         self
._OnAutoSelect
(match_field
, match_index
) 
2980                     if new_select_to 
!= newpos
: 
2981 ##                        dbg('queuing selection: (%d, %d)' % (newpos, new_select_to)) 
2982                         wx
.CallAfter(self
._SetSelection
, newpos
, new_select_to
) 
2984                         newfield 
= self
._FindField
(newpos
) 
2985                         if newfield 
!= field 
and newfield
._selectOnFieldEntry
: 
2986 ##                            dbg('queuing insertion point: (%d)' % newfield._extent[0]) 
2987                             wx
.CallAfter(self
._SetInsertionPoint
, newfield
._extent
[0]) 
2988 ##                            dbg('queuing selection: (%d, %d)' % (newfield._extent[0], newfield._extent[1])) 
2989                             wx
.CallAfter(self
._SetSelection
, newfield
._extent
[0], newfield
._extent
[1]) 
2991                             wx
.CallAfter(self
._SetSelection
, newpos
, new_select_to
) 
2992                     keep_processing 
= False 
2994             elif keep_processing
: 
2995 ##                dbg('char not allowed') 
2996                 keep_processing 
= False 
2997                 if (not wx
.Validator_IsSilent()) and orig_pos 
== pos
: 
3000         self
._applyFormatting
() 
3002         # Move to next insertion point 
3003         if keep_processing 
and key 
not in self
._nav
: 
3004             pos 
= self
._GetInsertionPoint
() 
3005             next_entry 
= self
._findNextEntry
( pos 
) 
3006             if pos 
!= next_entry
: 
3007 ##                dbg("moving from %(pos)d to next valid entry: %(next_entry)d" % locals()) 
3008                 wx
.CallAfter(self
._SetInsertionPoint
, next_entry 
) 
3010             if self
._isTemplateChar
(pos
): 
3011                 self
._AdjustField
(pos
) 
3015     def _FindFieldExtent(self
, pos
=None, getslice
=False, value
=None): 
3016         """ returns editable extent of field corresponding to 
3017         position pos, and, optionally, the contents of that field 
3018         in the control or the value specified. 
3019         Template chars are bound to the preceding field. 
3020         For masks beginning with template chars, these chars are ignored 
3021         when calculating the current field. 
3023         Eg: with template (###) ###-####, 
3024         >>> self._FindFieldExtent(pos=0) 
3026         >>> self._FindFieldExtent(pos=1) 
3028         >>> self._FindFieldExtent(pos=5) 
3030         >>> self._FindFieldExtent(pos=6) 
3032         >>> self._FindFieldExtent(pos=10) 
3036 ##        dbg('MaskedEditMixin::_FindFieldExtent(pos=%s, getslice=%s)' % (str(pos), str(getslice)) ,indent=1) 
3038         field 
= self
._FindField
(pos
) 
3041                 return None, None, "" 
3044         edit_start
, edit_end 
= field
._extent
 
3046             if value 
is None: value 
= self
._GetValue
() 
3047             slice = value
[edit_start
:edit_end
] 
3048 ##            dbg('edit_start:', edit_start, 'edit_end:', edit_end, 'slice: "%s"' % slice) 
3050             return edit_start
, edit_end
, slice 
3052 ##            dbg('edit_start:', edit_start, 'edit_end:', edit_end) 
3054             return edit_start
, edit_end
 
3057     def _FindField(self
, pos
=None): 
3059         Returns the field instance in which pos resides. 
3060         Template chars are bound to the preceding field. 
3061         For masks beginning with template chars, these chars are ignored 
3062         when calculating the current field. 
3065 ####        dbg('MaskedEditMixin::_FindField(pos=%s)' % str(pos) ,indent=1) 
3066         if pos 
is None: pos 
= self
._GetInsertionPoint
() 
3067         elif pos 
< 0 or pos 
> self
._masklength
: 
3068             raise IndexError('position %s out of range of control' % str(pos
)) 
3070         if len(self
._fields
) == 0: 
3076         return self
._fields
[self
._lookupField
[pos
]] 
3079     def ClearValue(self
): 
3080         """ Blanks the current control value by replacing it with the default value.""" 
3081 ##        dbg("MaskedEditMixin::ClearValue - value reset to default value (template)") 
3082         self
._SetValue
( self
._template 
) 
3083         self
._SetInsertionPoint
(0) 
3087     def _baseCtrlEventHandler(self
, event
): 
3089         This function is used whenever a key should be handled by the base control. 
3095     def _OnUpNumeric(self
, event
): 
3097         Makes up-arrow act like shift-tab should; ie. take you to start of 
3100 ##        dbg('MaskedEditMixin::_OnUpNumeric', indent=1) 
3101         event
.m_shiftDown 
= 1 
3102 ##        dbg('event.ShiftDown()?', event.ShiftDown()) 
3103         self
._OnChangeField
(event
) 
3107     def _OnArrow(self
, event
): 
3109         Used in response to left/right navigation keys; makes these actions skip 
3110         over mask template chars. 
3112 ##        dbg("MaskedEditMixin::_OnArrow", indent=1) 
3113         pos 
= self
._GetInsertionPoint
() 
3114         keycode 
= event
.GetKeyCode() 
3115         sel_start
, sel_to 
= self
._GetSelection
() 
3116         entry_end 
= self
._goEnd
(getPosOnly
=True) 
3117         if keycode 
in (wx
.WXK_RIGHT
, wx
.WXK_DOWN
): 
3118             if( ( not self
._isTemplateChar
(pos
) and pos
+1 > entry_end
) 
3119                 or ( self
._isTemplateChar
(pos
) and pos 
>= entry_end
) ): 
3120 ##                dbg("can't advance", indent=0) 
3122             elif self
._isTemplateChar
(pos
): 
3123                 self
._AdjustField
(pos
) 
3124         elif keycode 
in (wx
.WXK_LEFT
,wx
.WXK_UP
) and sel_start 
== sel_to 
and pos 
> 0 and self
._isTemplateChar
(pos
-1): 
3125 ##            dbg('adjusting field') 
3126             self
._AdjustField
(pos
) 
3128         # treat as shifted up/down arrows as tab/reverse tab: 
3129         if event
.ShiftDown() and keycode 
in (wx
.WXK_UP
, wx
.WXK_DOWN
): 
3130             # remove "shifting" and treat as (forward) tab: 
3131             event
.m_shiftDown 
= False 
3132             keep_processing 
= self
._OnChangeField
(event
) 
3134         elif self
._FindField
(pos
)._selectOnFieldEntry
: 
3135             if( keycode 
in (wx
.WXK_UP
, wx
.WXK_LEFT
) 
3137                 and self
._isTemplateChar
(sel_start
-1) 
3138                 and sel_start 
!= self
._masklength
 
3139                 and not self
._signOk 
and not self
._useParens
): 
3141                 # call _OnChangeField to handle "ctrl-shifted event" 
3142                 # (which moves to previous field and selects it.) 
3143                 event
.m_shiftDown 
= True 
3144                 event
.m_ControlDown 
= True 
3145                 keep_processing 
= self
._OnChangeField
(event
) 
3146             elif( keycode 
in (wx
.WXK_DOWN
, wx
.WXK_RIGHT
) 
3147                   and sel_to 
!= self
._masklength
 
3148                   and self
._isTemplateChar
(sel_to
)): 
3150                 # when changing field to the right, ensure don't accidentally go left instead 
3151                 event
.m_shiftDown 
= False 
3152                 keep_processing 
= self
._OnChangeField
(event
) 
3154                 # treat arrows as normal, allowing selection 
3156 ##                dbg('using base ctrl event processing') 
3159             if( (sel_to 
== self
._fields
[0]._extent
[0] and keycode 
== wx
.WXK_LEFT
) 
3160                 or (sel_to 
== self
._masklength 
and keycode 
== wx
.WXK_RIGHT
) ): 
3161                 if not wx
.Validator_IsSilent(): 
3164                 # treat arrows as normal, allowing selection 
3166 ##                dbg('using base event processing') 
3169         keep_processing 
= False 
3171         return keep_processing
 
3174     def _OnCtrl_S(self
, event
): 
3175         """ Default Ctrl-S handler; prints value information if demo enabled. """ 
3176 ##        dbg("MaskedEditMixin::_OnCtrl_S") 
3178             print 'MaskedEditMixin.GetValue()       = "%s"\nMaskedEditMixin.GetPlainValue() = "%s"' % (self
.GetValue(), self
.GetPlainValue()) 
3179             print "Valid? => " + str(self
.IsValid()) 
3180             print "Current field, start, end, value =", str( self
._FindFieldExtent
(getslice
=True)) 
3184     def _OnCtrl_X(self
, event
=None): 
3185         """ Handles ctrl-x keypress in control and Cut operation on context menu. 
3186             Should return False to skip other processing. """ 
3187 ##        dbg("MaskedEditMixin::_OnCtrl_X", indent=1) 
3192     def _OnCtrl_C(self
, event
=None): 
3193         """ Handles ctrl-C keypress in control and Copy operation on context menu. 
3194             Uses base control handling. Should return False to skip other processing.""" 
3198     def _OnCtrl_V(self
, event
=None): 
3199         """ Handles ctrl-V keypress in control and Paste operation on context menu. 
3200             Should return False to skip other processing. """ 
3201 ##        dbg("MaskedEditMixin::_OnCtrl_V", indent=1) 
3206     def _OnInsert(self
, event
=None): 
3207         """ Handles shift-insert and control-insert operations (paste and copy, respectively)""" 
3208 ##        dbg("MaskedEditMixin::_OnInsert", indent=1) 
3209         if event 
and isinstance(event
, wx
.KeyEvent
): 
3210             if event
.ShiftDown(): 
3212             elif event
.ControlDown(): 
3219     def _OnDelete(self
, event
=None): 
3220         """ Handles shift-delete and delete operations (cut and erase, respectively)""" 
3221 ##        dbg("MaskedEditMixin::_OnDelete", indent=1) 
3222         if event 
and isinstance(event
, wx
.KeyEvent
): 
3223             if event
.ShiftDown(): 
3226                 self
._OnErase
(event
) 
3228             self
._OnErase
(event
) 
3232     def _OnCtrl_Z(self
, event
=None): 
3233         """ Handles ctrl-Z keypress in control and Undo operation on context menu. 
3234             Should return False to skip other processing. """ 
3235 ##        dbg("MaskedEditMixin::_OnCtrl_Z", indent=1) 
3240     def _OnCtrl_A(self
,event
=None): 
3241         """ Handles ctrl-a keypress in control. Should return False to skip other processing. """ 
3242         end 
= self
._goEnd
(getPosOnly
=True) 
3243         if not event 
or (isinstance(event
, wx
.KeyEvent
) and event
.ShiftDown()): 
3244             wx
.CallAfter(self
._SetInsertionPoint
, 0) 
3245             wx
.CallAfter(self
._SetSelection
, 0, self
._masklength
) 
3247             wx
.CallAfter(self
._SetInsertionPoint
, 0) 
3248             wx
.CallAfter(self
._SetSelection
, 0, end
) 
3252     def _OnErase(self
, event
=None, just_return_value
=False): 
3253         """ Handles backspace and delete keypress in control. Should return False to skip other processing.""" 
3254 ##        dbg("MaskedEditMixin::_OnErase", indent=1) 
3255         sel_start
, sel_to 
= self
._GetSelection
()                   ## check for a range of selected text 
3257         if event 
is None:   # called as action routine from Cut() operation. 
3260             key 
= event
.GetKeyCode() 
3262         field 
= self
._FindField
(sel_to
) 
3263         start
, end 
= field
._extent
 
3264         value 
= self
._GetValue
() 
3265         oldstart 
= sel_start
 
3267         # If trying to erase beyond "legal" bounds, disallow operation: 
3268         if( (sel_to 
== 0 and key 
== wx
.WXK_BACK
) 
3269             or (self
._signOk 
and sel_to 
== 1 and value
[0] == ' ' and key 
== wx
.WXK_BACK
) 
3270             or (sel_to 
== self
._masklength 
and sel_start 
== sel_to 
and key 
== wx
.WXK_DELETE 
and not field
._insertRight
) 
3271             or (self
._signOk 
and self
._useParens
 
3272                 and sel_start 
== sel_to
 
3273                 and sel_to 
== self
._masklength 
- 1 
3274                 and value
[sel_to
] == ' ' and key 
== wx
.WXK_DELETE 
and not field
._insertRight
) ): 
3275             if not wx
.Validator_IsSilent(): 
3281         if( field
._insertRight                                  
# an insert-right field 
3282             and value
[start
:end
] != self
._template
[start
:end
]   # and field not empty 
3283             and sel_start 
>= start                              
# and selection starts in field 
3284             and ((sel_to 
== sel_start                           
# and no selection 
3285                   and sel_to 
== end                             
# and cursor at right edge 
3286                   and key 
in (wx
.WXK_BACK
, wx
.WXK_DELETE
))            # and either delete or backspace key 
3288                  (key 
== wx
.WXK_BACK                               
# backspacing 
3289                     and (sel_to 
== end                          
# and selection ends at right edge 
3290                          or sel_to 
< end 
and field
._allowInsert
)) ) ):  # or allow right insert at any point in field 
3292 ##            dbg('delete left') 
3293             # if backspace but left of cursor is empty, adjust cursor right before deleting 
3294             while( key 
== wx
.WXK_BACK
 
3295                    and sel_start 
== sel_to
 
3297                    and value
[start
:sel_start
] == self
._template
[start
:sel_start
]): 
3301 ##            dbg('sel_start, start:', sel_start, start) 
3303             if sel_start 
== sel_to
: 
3307             newfield 
= value
[start
:keep
] + value
[sel_to
:end
] 
3309             # handle sign char moving from outside field into the field: 
3310             move_sign_into_field 
= False 
3311             if not field
._padZero 
and self
._signOk 
and self
._isNeg 
and value
[0] in ('-', '('): 
3313                 newfield 
= signchar 
+ newfield
 
3314                 move_sign_into_field 
= True 
3315 ##            dbg('cut newfield: "%s"' % newfield) 
3317             # handle what should fill in from the left: 
3319             for i 
in range(start
, end 
- len(newfield
)): 
3322                 elif( self
._signOk 
and self
._isNeg 
and i 
== 1 
3323                       and ((self
._useParens 
and newfield
.find('(') == -1) 
3324                            or (not self
._useParens 
and newfield
.find('-') == -1)) ): 
3327                     left 
+= self
._template
[i
]   # this can produce strange results in combination with default values... 
3328             newfield 
= left 
+ newfield
 
3329 ##            dbg('filled newfield: "%s"' % newfield) 
3331             newstr 
= value
[:start
] + newfield 
+ value
[end
:] 
3333             # (handle sign located in "mask position" in front of field prior to delete) 
3334             if move_sign_into_field
: 
3335                 newstr 
= ' ' + newstr
[1:] 
3338             # handle erasure of (left) sign, moving selection accordingly... 
3339             if self
._signOk 
and sel_start 
== 0: 
3340                 newstr 
= value 
= ' ' + value
[1:] 
3343             if field
._allowInsert 
and sel_start 
>= start
: 
3344                 # selection (if any) falls within current insert-capable field: 
3345                 select_len 
= sel_to 
- sel_start
 
3346                 # determine where cursor should end up: 
3347                 if key 
== wx
.WXK_BACK
: 
3349                         newpos 
= sel_start 
-1 
3355                     if sel_to 
== sel_start
: 
3356                         erase_to 
= sel_to 
+ 1 
3360                 if self
._isTemplateChar
(newpos
) and select_len 
== 0: 
3362                         if value
[newpos
] in ('(', '-'): 
3363                             newpos 
+= 1     # don't move cusor 
3364                             newstr 
= ' ' + value
[newpos
:] 
3365                         elif value
[newpos
] == ')': 
3366                             # erase right sign, but don't move cursor; (matching left sign handled later) 
3367                             newstr 
= value
[:newpos
] + ' ' 
3369                             # no deletion; just move cursor 
3372                         # no deletion; just move cursor 
3375                     if erase_to 
> end
: erase_to 
= end
 
3376                     erase_len 
= erase_to 
- newpos
 
3378                     left 
= value
[start
:newpos
] 
3379 ##                    dbg("retained ='%s'" % value[erase_to:end], 'sel_to:', sel_to, "fill: '%s'" % self._template[end - erase_len:end]) 
3380                     right 
= value
[erase_to
:end
] + self
._template
[end
-erase_len
:end
] 
3382                     if field
._alignRight
: 
3383                         rstripped 
= right
.rstrip() 
3384                         if rstripped 
!= right
: 
3385                             pos_adjust 
= len(right
) - len(rstripped
) 
3388                     if not field
._insertRight 
and value
[-1] == ')' and end 
== self
._masklength 
- 1: 
3389                         # need to shift ) into the field: 
3390                         right 
= right
[:-1] + ')' 
3391                         value 
= value
[:-1] + ' ' 
3393                     newfield 
= left
+right
 
3395                         newfield 
= newfield
.rjust(end
-start
) 
3396                         newpos 
+= pos_adjust
 
3397 ##                    dbg("left='%s', right ='%s', newfield='%s'" %(left, right, newfield)) 
3398                     newstr 
= value
[:start
] + newfield 
+ value
[end
:] 
3403                 if sel_start 
== sel_to
: 
3404 ##                    dbg("current sel_start, sel_to:", sel_start, sel_to) 
3405                     if key 
== wx
.WXK_BACK
: 
3406                         sel_start
, sel_to 
= sel_to
-1, sel_to
-1 
3407 ##                        dbg("new sel_start, sel_to:", sel_start, sel_to) 
3409                     if field
._padZero 
and not value
[start
:sel_to
].replace('0', '').replace(' ','').replace(field
._fillChar
, ''): 
3410                         # preceding chars (if any) are zeros, blanks or fillchar; new char should be 0: 
3413                         newchar 
= self
._template
[sel_to
] ## get an original template character to "clear" the current char 
3414 ##                    dbg('value = "%s"' % value, 'value[%d] = "%s"' %(sel_start, value[sel_start])) 
3416                     if self
._isTemplateChar
(sel_to
): 
3417                         if sel_to 
== 0 and self
._signOk 
and value
[sel_to
] == '-':   # erasing "template" sign char 
3418                             newstr 
= ' ' + value
[1:] 
3420                         elif self
._signOk 
and self
._useParens 
and (value
[sel_to
] == ')' or value
[sel_to
] == '('): 
3421                             # allow "change sign" by removing both parens: 
3422                             newstr 
= value
[:self
._signpos
] + ' ' + value
[self
._signpos
+1:-1] + ' ' 
3427                         if field
._insertRight 
and sel_start 
== sel_to
: 
3428                             # force non-insert-right behavior, by selecting char to be replaced: 
3430                         newstr
, ignore 
= self
._insertKey
(newchar
, sel_start
, sel_start
, sel_to
, value
) 
3434                     newstr 
= self
._eraseSelection
(value
, sel_start
, sel_to
) 
3436                 pos 
= sel_start  
# put cursor back at beginning of selection 
3438         if self
._signOk 
and self
._useParens
: 
3439             # account for resultant unbalanced parentheses: 
3440             left_signpos 
= newstr
.find('(') 
3441             right_signpos 
= newstr
.find(')') 
3443             if left_signpos 
== -1 and right_signpos 
!= -1: 
3444                 # erased left-sign marker; get rid of right sign marker: 
3445                 newstr 
= newstr
[:right_signpos
] + ' ' + newstr
[right_signpos
+1:] 
3447             elif left_signpos 
!= -1 and right_signpos 
== -1: 
3448                 # erased right-sign marker; get rid of left-sign marker: 
3449                 newstr 
= newstr
[:left_signpos
] + ' ' + newstr
[left_signpos
+1:] 
3451 ##        dbg("oldstr:'%s'" % value, 'oldpos:', oldstart) 
3452 ##        dbg("newstr:'%s'" % newstr, 'pos:', pos) 
3454         # if erasure results in an invalid field, disallow it: 
3455 ##        dbg('field._validRequired?', field._validRequired) 
3456 ##        dbg('field.IsValid("%s")?' % newstr[start:end], field.IsValid(newstr[start:end])) 
3457         if field
._validRequired 
and not field
.IsValid(newstr
[start
:end
]): 
3458             if not wx
.Validator_IsSilent(): 
3463         # if erasure results in an invalid value, disallow it: 
3464         if self
._ctrl
_constraints
._validRequired 
and not self
.IsValid(newstr
): 
3465             if not wx
.Validator_IsSilent(): 
3470         if just_return_value
: 
3475 ##        dbg('setting value (later) to', newstr) 
3476         wx
.CallAfter(self
._SetValue
, newstr
) 
3477 ##        dbg('setting insertion point (later) to', pos) 
3478         wx
.CallAfter(self
._SetInsertionPoint
, pos
) 
3481             self
.modified 
= True 
3485     def _OnEnd(self
,event
): 
3486         """ Handles End keypress in control. Should return False to skip other processing. """ 
3487 ##        dbg("MaskedEditMixin::_OnEnd", indent=1) 
3488         pos 
= self
._adjustPos
(self
._GetInsertionPoint
(), event
.GetKeyCode()) 
3489         if not event
.ControlDown(): 
3490             end 
= self
._masklength  
# go to end of control 
3491             if self
._signOk 
and self
._useParens
: 
3492                 end 
= end 
- 1       # account for reserved char at end 
3494             end_of_input 
= self
._goEnd
(getPosOnly
=True) 
3495             sel_start
, sel_to 
= self
._GetSelection
() 
3496             if sel_to 
< pos
: sel_to 
= pos
 
3497             field 
= self
._FindField
(sel_to
) 
3498             field_end 
= self
._FindField
(end_of_input
) 
3500             # pick different end point if either: 
3501             # - cursor not in same field 
3502             # - or at or past last input already 
3503             # - or current selection = end of current field: 
3504 ####            dbg('field != field_end?', field != field_end) 
3505 ####            dbg('sel_to >= end_of_input?', sel_to >= end_of_input) 
3506             if field 
!= field_end 
or sel_to 
>= end_of_input
: 
3507                 edit_start
, edit_end 
= field
._extent
 
3508 ####                dbg('edit_end:', edit_end) 
3509 ####                dbg('sel_to:', sel_to) 
3510 ####                dbg('sel_to == edit_end?', sel_to == edit_end) 
3511 ####                dbg('field._index < self._field_indices[-1]?', field._index < self._field_indices[-1]) 
3513                 if sel_to 
== edit_end 
and field
._index 
< self
._field
_indices
[-1]: 
3514                     edit_start
, edit_end 
= self
._FindFieldExtent
(self
._findNextEntry
(edit_end
))  # go to end of next field: 
3516 ##                    dbg('end moved to', end) 
3518                 elif sel_to 
== edit_end 
and field
._index 
== self
._field
_indices
[-1]: 
3519                     # already at edit end of last field; select to end of control: 
3520                     end 
= self
._masklength
 
3521 ##                    dbg('end moved to', end) 
3523                     end 
= edit_end  
# select to end of current field 
3524 ##                    dbg('end moved to ', end) 
3526                 # select to current end of input 
3530 ####        dbg('pos:', pos, 'end:', end) 
3532         if event
.ShiftDown(): 
3533             if not event
.ControlDown(): 
3534 ##                dbg("shift-end; select to end of control") 
3537 ##                dbg("shift-ctrl-end; select to end of non-whitespace") 
3539             wx
.CallAfter(self
._SetInsertionPoint
, pos
) 
3540             wx
.CallAfter(self
._SetSelection
, pos
, end
) 
3542             if not event
.ControlDown(): 
3543 ##                dbg('go to end of control:') 
3545             wx
.CallAfter(self
._SetInsertionPoint
, end
) 
3546             wx
.CallAfter(self
._SetSelection
, end
, end
) 
3552     def _OnReturn(self
, event
): 
3554          Swallows the return, issues a Navigate event instead, since 
3555          masked controls are "single line" by defn. 
3557 ##         dbg('MaskedEditMixin::OnReturn') 
3562     def _OnHome(self
,event
): 
3563         """ Handles Home keypress in control. Should return False to skip other processing.""" 
3564 ##        dbg("MaskedEditMixin::_OnHome", indent=1) 
3565         pos 
= self
._adjustPos
(self
._GetInsertionPoint
(), event
.GetKeyCode()) 
3566         sel_start
, sel_to 
= self
._GetSelection
() 
3568         # There are 5 cases here: 
3570         # 1) shift: select from start of control to end of current 
3572         if event
.ShiftDown() and not event
.ControlDown(): 
3573 ##            dbg("shift-home; select to start of control") 
3577         # 2) no shift, no control: move cursor to beginning of control. 
3578         elif not event
.ControlDown(): 
3579 ##            dbg("home; move to start of control") 
3583         # 3) No shift, control: move cursor back to beginning of field; if 
3584         #    there already, go to beginning of previous field. 
3585         # 4) shift, control, start of selection not at beginning of control: 
3586         #    move sel_start back to start of field; if already there, go to 
3587         #    start of previous field. 
3588         elif( event
.ControlDown() 
3589               and (not event
.ShiftDown() 
3590                    or (event
.ShiftDown() and sel_start 
> 0) ) ): 
3591             if len(self
._field
_indices
) > 1: 
3592                 field 
= self
._FindField
(sel_start
) 
3593                 start
, ignore 
= field
._extent
 
3594                 if sel_start 
== start 
and field
._index 
!= self
._field
_indices
[0]:  # go to start of previous field: 
3595                     start
, ignore 
= self
._FindFieldExtent
(sel_start
-1) 
3596                 elif sel_start 
== start
: 
3597                     start 
= 0   # go to literal beginning if edit start 
3604             if not event
.ShiftDown(): 
3605 ##                dbg("ctrl-home; move to beginning of field") 
3608 ##                dbg("shift-ctrl-home; select to beginning of field") 
3612         # 5) shift, control, start of selection at beginning of control: 
3613         #    unselect by moving sel_to backward to beginning of current field; 
3614         #    if already there, move to start of previous field. 
3616             if len(self
._field
_indices
) > 1: 
3617                 # find end of previous field: 
3618                 field 
= self
._FindField
(sel_to
) 
3619                 if sel_to 
> start 
and field
._index 
!= self
._field
_indices
[0]: 
3620                     ignore
, end 
= self
._FindFieldExtent
(field
._extent
[0]-1) 
3626                 end_of_field 
= False 
3627 ##            dbg("shift-ctrl-home; unselect to beginning of field") 
3629 ##        dbg('queuing new sel_start, sel_to:', (start, end)) 
3630         wx
.CallAfter(self
._SetInsertionPoint
, start
) 
3631         wx
.CallAfter(self
._SetSelection
, start
, end
) 
3636     def _OnChangeField(self
, event
): 
3638         Primarily handles TAB events, but can be used for any key that 
3639         designer wants to change fields within a masked edit control. 
3641 ##        dbg('MaskedEditMixin::_OnChangeField', indent = 1) 
3642         # determine end of current field: 
3643         pos 
= self
._GetInsertionPoint
() 
3644 ##        dbg('current pos:', pos) 
3645         sel_start
, sel_to 
= self
._GetSelection
() 
3647         if self
._masklength 
< 0:   # no fields; process tab normally 
3648             self
._AdjustField
(pos
) 
3649             if event
.GetKeyCode() == wx
.WXK_TAB
: 
3650 ##                dbg('tab to next ctrl') 
3651                 # As of 2.5.2, you don't call event.Skip() to do 
3652                 # this, but instead force explicit navigation, if 
3653                 # wx.TE_PROCESS_TAB is used (like in the masked edits) 
3660         if event
.ShiftDown(): 
3664             # NOTE: doesn't yet work with SHIFT-tab under wx; the control 
3665             # never sees this event! (But I've coded for it should it ever work, 
3666             # and it *does* work for '.' in IpAddrCtrl.) 
3667             field 
= self
._FindField
(pos
) 
3668             index 
= field
._index
 
3669             field_start 
= field
._extent
[0] 
3670             if pos 
< field_start
: 
3671 ##                dbg('cursor before 1st field; cannot change to a previous field') 
3672                 if not wx
.Validator_IsSilent(): 
3676             if event
.ControlDown(): 
3677 ##                dbg('queuing select to beginning of field:', field_start, pos) 
3678                 wx
.CallAfter(self
._SetInsertionPoint
, field_start
) 
3679                 wx
.CallAfter(self
._SetSelection
, field_start
, pos
) 
3684                   # We're already in the 1st field; process shift-tab normally: 
3685                 self
._AdjustField
(pos
) 
3686                 if event
.GetKeyCode() == wx
.WXK_TAB
: 
3687 ##                    dbg('tab to previous ctrl') 
3688                     # As of 2.5.2, you don't call event.Skip() to do 
3689                     # this, but instead force explicit navigation, if 
3690                     # wx.TE_PROCESS_TAB is used (like in the masked edits) 
3691                     self
.Navigate(False) 
3693 ##                    dbg('position at beginning') 
3694                     wx
.CallAfter(self
._SetInsertionPoint
, field_start
) 
3698                 # find beginning of previous field: 
3699                 begin_prev 
= self
._FindField
(field_start
-1)._extent
[0] 
3700                 self
._AdjustField
(pos
) 
3701 ##                dbg('repositioning to', begin_prev) 
3702                 wx
.CallAfter(self
._SetInsertionPoint
, begin_prev
) 
3703                 if self
._FindField
(begin_prev
)._selectOnFieldEntry
: 
3704                     edit_start
, edit_end 
= self
._FindFieldExtent
(begin_prev
) 
3705 ##                    dbg('queuing selection to (%d, %d)' % (edit_start, edit_end)) 
3706                     wx
.CallAfter(self
._SetInsertionPoint
, edit_start
) 
3707                     wx
.CallAfter(self
._SetSelection
, edit_start
, edit_end
) 
3713             field 
= self
._FindField
(sel_to
) 
3714             field_start
, field_end 
= field
._extent
 
3715             if event
.ControlDown(): 
3716 ##                dbg('queuing select to end of field:', pos, field_end) 
3717                 wx
.CallAfter(self
._SetInsertionPoint
, pos
) 
3718                 wx
.CallAfter(self
._SetSelection
, pos
, field_end
) 
3722                 if pos 
< field_start
: 
3723 ##                    dbg('cursor before 1st field; go to start of field') 
3724                     wx
.CallAfter(self
._SetInsertionPoint
, field_start
) 
3725                     if field
._selectOnFieldEntry
: 
3726                         wx
.CallAfter(self
._SetSelection
, field_start
, field_end
) 
3728                         wx
.CallAfter(self
._SetSelection
, field_start
, field_start
) 
3731 ##                dbg('end of current field:', field_end) 
3732 ##                dbg('go to next field') 
3733                 if field_end 
== self
._fields
[self
._field
_indices
[-1]]._extent
[1]: 
3734                     self
._AdjustField
(pos
) 
3735                     if event
.GetKeyCode() == wx
.WXK_TAB
: 
3736 ##                        dbg('tab to next ctrl') 
3737                         # As of 2.5.2, you don't call event.Skip() to do 
3738                         # this, but instead force explicit navigation, if 
3739                         # wx.TE_PROCESS_TAB is used (like in the masked edits) 
3742 ##                        dbg('position at end') 
3743                         wx
.CallAfter(self
._SetInsertionPoint
, field_end
) 
3747                     # we have to find the start of the next field 
3748                     next_pos 
= self
._findNextEntry
(field_end
) 
3749                     if next_pos 
== field_end
: 
3750 ##                        dbg('already in last field') 
3751                         self
._AdjustField
(pos
) 
3752                         if event
.GetKeyCode() == wx
.WXK_TAB
: 
3753 ##                            dbg('tab to next ctrl') 
3754                             # As of 2.5.2, you don't call event.Skip() to do 
3755                             # this, but instead force explicit navigation, if 
3756                             # wx.TE_PROCESS_TAB is used (like in the masked edits) 
3762                         self
._AdjustField
( pos 
) 
3764                         # move cursor to appropriate point in the next field and select as necessary: 
3765                         field 
= self
._FindField
(next_pos
) 
3766                         edit_start
, edit_end 
= field
._extent
 
3767                         if field
._selectOnFieldEntry
: 
3768 ##                            dbg('move to ', next_pos) 
3769                             wx
.CallAfter(self
._SetInsertionPoint
, next_pos
) 
3770                             edit_start
, edit_end 
= self
._FindFieldExtent
(next_pos
) 
3771 ##                            dbg('queuing select', edit_start, edit_end) 
3772                             wx
.CallAfter(self
._SetSelection
, edit_start
, edit_end
) 
3774                             if field
._insertRight
: 
3775                                 next_pos 
= field
._extent
[1] 
3776 ##                            dbg('move to ', next_pos) 
3777                             wx
.CallAfter(self
._SetInsertionPoint
, next_pos
) 
3782     def _OnDecimalPoint(self
, event
): 
3783 ##        dbg('MaskedEditMixin::_OnDecimalPoint', indent=1) 
3785         pos 
= self
._adjustPos
(self
._GetInsertionPoint
(), event
.GetKeyCode()) 
3787         if self
._isFloat
:       ## handle float value, move to decimal place 
3788 ##            dbg('key == Decimal tab; decimal pos:', self._decimalpos) 
3789             value 
= self
._GetValue
() 
3790             if pos 
< self
._decimalpos
: 
3791                 clipped_text 
= value
[0:pos
] + self
._decimalChar 
+ value
[self
._decimalpos
+1:] 
3792 ##                dbg('value: "%s"' % self._GetValue(), "clipped_text:'%s'" % clipped_text) 
3793                 newstr 
= self
._adjustFloat
(clipped_text
) 
3795                 newstr 
= self
._adjustFloat
(value
) 
3796             wx
.CallAfter(self
._SetValue
, newstr
) 
3797             fraction 
= self
._fields
[1] 
3798             start
, end 
= fraction
._extent
 
3799             wx
.CallAfter(self
._SetInsertionPoint
, start
) 
3800             if fraction
._selectOnFieldEntry
: 
3801 ##                dbg('queuing selection after decimal point to:', (start, end)) 
3802                 wx
.CallAfter(self
._SetSelection
, start
, end
) 
3804                 wx
.CallAfter(self
._SetSelection
, start
, start
) 
3805             keep_processing 
= False 
3807         if self
._isInt
:      ## handle integer value, truncate from current position 
3808 ##            dbg('key == Integer decimal event') 
3809             value 
= self
._GetValue
() 
3810             clipped_text 
= value
[0:pos
] 
3811 ##            dbg('value: "%s"' % self._GetValue(), "clipped_text:'%s'" % clipped_text) 
3812             newstr 
= self
._adjustInt
(clipped_text
) 
3813 ##            dbg('newstr: "%s"' % newstr) 
3814             wx
.CallAfter(self
._SetValue
, newstr
) 
3815             newpos 
= len(newstr
.rstrip()) 
3816             if newstr
.find(')') != -1: 
3817                 newpos 
-= 1     # (don't move past right paren) 
3818             wx
.CallAfter(self
._SetInsertionPoint
, newpos
) 
3819             wx
.CallAfter(self
._SetSelection
, newpos
, newpos
) 
3820             keep_processing 
= False 
3824     def _OnChangeSign(self
, event
): 
3825 ##        dbg('MaskedEditMixin::_OnChangeSign', indent=1) 
3826         key 
= event
.GetKeyCode() 
3827         pos 
= self
._adjustPos
(self
._GetInsertionPoint
(), key
) 
3828         value 
= self
._eraseSelection
() 
3829         integer 
= self
._fields
[0] 
3830         start
, end 
= integer
._extent
 
3831         sel_start
, sel_to 
= self
._GetSelection
() 
3833 ####        dbg('adjusted pos:', pos) 
3834         if chr(key
) in ('-','+','(', ')') or (chr(key
) == " " and pos 
== self
._signpos
): 
3835             cursign 
= self
._isNeg
 
3836 ##            dbg('cursign:', cursign) 
3837             if chr(key
) in ('-','(', ')'): 
3838                 if sel_start 
<= self
._signpos
: 
3841                     self
._isNeg 
= (not self
._isNeg
)   ## flip value 
3844 ##            dbg('isNeg?', self._isNeg) 
3846             text
, self
._signpos
, self
._right
_signpos 
= self
._getSignedValue
(candidate
=value
) 
3847 ##            dbg('text:"%s"' % text, 'signpos:', self._signpos, 'right_signpos:', self._right_signpos) 
3851             if self
._isNeg 
and self
._signpos 
is not None and self
._signpos 
!= -1: 
3852                 if self
._useParens 
and self
._right
_signpos 
is not None: 
3853                     text 
= text
[:self
._signpos
] + '(' + text
[self
._signpos
+1:self
._right
_signpos
] + ')' + text
[self
._right
_signpos
+1:] 
3855                     text 
= text
[:self
._signpos
] + '-' + text
[self
._signpos
+1:] 
3857 ####                dbg('self._isNeg?', self._isNeg, 'self.IsValid(%s)' % text, self.IsValid(text)) 
3859                     text 
= text
[:self
._signpos
] + ' ' + text
[self
._signpos
+1:self
._right
_signpos
] + ' ' + text
[self
._right
_signpos
+1:] 
3861                     text 
= text
[:self
._signpos
] + ' ' + text
[self
._signpos
+1:] 
3862 ##                dbg('clearing self._isNeg') 
3865             wx
.CallAfter(self
._SetValue
, text
) 
3866             wx
.CallAfter(self
._applyFormatting
) 
3867 ##            dbg('pos:', pos, 'signpos:', self._signpos) 
3868             if pos 
== self
._signpos 
or integer
.IsEmpty(text
[start
:end
]): 
3869                 wx
.CallAfter(self
._SetInsertionPoint
, self
._signpos
+1) 
3871                 wx
.CallAfter(self
._SetInsertionPoint
, pos
) 
3873             keep_processing 
= False 
3875             keep_processing 
= True 
3877         return keep_processing
 
3880     def _OnGroupChar(self
, event
): 
3882         This handler is only registered if the mask is a numeric mask. 
3883         It allows the insertion of ',' or '.' if appropriate. 
3885 ##        dbg('MaskedEditMixin::_OnGroupChar', indent=1) 
3886         keep_processing 
= True 
3887         pos 
= self
._adjustPos
(self
._GetInsertionPoint
(), event
.GetKeyCode()) 
3888         sel_start
, sel_to 
= self
._GetSelection
() 
3889         groupchar 
= self
._fields
[0]._groupChar
 
3890         if not self
._isCharAllowed
(groupchar
, pos
, checkRegex
=True): 
3891             keep_processing 
= False 
3892             if not wx
.Validator_IsSilent(): 
3896             newstr
, newpos 
= self
._insertKey
(groupchar
, pos
, sel_start
, sel_to
, self
._GetValue
() ) 
3897 ##            dbg("str with '%s' inserted:" % groupchar, '"%s"' % newstr) 
3898             if self
._ctrl
_constraints
._validRequired 
and not self
.IsValid(newstr
): 
3899                 keep_processing 
= False 
3900                 if not wx
.Validator_IsSilent(): 
3904             wx
.CallAfter(self
._SetValue
, newstr
) 
3905             wx
.CallAfter(self
._SetInsertionPoint
, newpos
) 
3906         keep_processing 
= False 
3908         return keep_processing
 
3911     def _findNextEntry(self
,pos
, adjustInsert
=True): 
3912         """ Find the insertion point for the next valid entry character position.""" 
3913         if self
._isTemplateChar
(pos
):   # if changing fields, pay attn to flag 
3914             adjustInsert 
= adjustInsert
 
3915         else:                           # else within a field; flag not relevant 
3916             adjustInsert 
= False 
3918         while self
._isTemplateChar
(pos
) and pos 
< self
._masklength
: 
3921         # if changing fields, and we've been told to adjust insert point, 
3922         # look at new field; if empty and right-insert field, 
3923         # adjust to right edge: 
3924         if adjustInsert 
and pos 
< self
._masklength
: 
3925             field 
= self
._FindField
(pos
) 
3926             start
, end 
= field
._extent
 
3927             slice = self
._GetValue
()[start
:end
] 
3928             if field
._insertRight 
and field
.IsEmpty(slice): 
3933     def _findNextTemplateChar(self
, pos
): 
3934         """ Find the position of the next non-editable character in the mask.""" 
3935         while not self
._isTemplateChar
(pos
) and pos 
< self
._masklength
: 
3940     def _OnAutoCompleteField(self
, event
): 
3941 ##        dbg('MaskedEditMixin::_OnAutoCompleteField', indent =1) 
3942         pos 
= self
._GetInsertionPoint
() 
3943         field 
= self
._FindField
(pos
) 
3944         edit_start
, edit_end
, slice = self
._FindFieldExtent
(pos
, getslice
=True) 
3947         keycode 
= event
.GetKeyCode() 
3949         if field
._fillChar 
!= ' ': 
3950             text 
= slice.replace(field
._fillChar
, '') 
3954         keep_processing 
= True  # (assume True to start) 
3955 ##        dbg('field._hasList?', field._hasList) 
3957 ##            dbg('choices:', field._choices) 
3958 ##            dbg('compareChoices:', field._compareChoices) 
3959             choices
, choice_required 
= field
._compareChoices
, field
._choiceRequired
 
3960             if keycode 
in (wx
.WXK_PRIOR
, wx
.WXK_UP
): 
3964             match_index
, partial_match 
= self
._autoComplete
(direction
, choices
, text
, compareNoCase
=field
._compareNoCase
, current_index 
= field
._autoCompleteIndex
) 
3965             if( match_index 
is None 
3966                 and (keycode 
in self
._autoCompleteKeycodes 
+ [wx
.WXK_PRIOR
, wx
.WXK_NEXT
] 
3967                      or (keycode 
in [wx
.WXK_UP
, wx
.WXK_DOWN
] and event
.ShiftDown() ) ) ): 
3968                 # Select the 1st thing from the list: 
3971             if( match_index 
is not None 
3972                 and ( keycode 
in self
._autoCompleteKeycodes 
+ [wx
.WXK_PRIOR
, wx
.WXK_NEXT
] 
3973                       or (keycode 
in [wx
.WXK_UP
, wx
.WXK_DOWN
] and event
.ShiftDown()) 
3974                       or (keycode 
== wx
.WXK_DOWN 
and partial_match
) ) ): 
3976                 # We're allowed to auto-complete: 
3977 ##                dbg('match found') 
3978                 value 
= self
._GetValue
() 
3979                 newvalue 
= value
[:edit_start
] + field
._choices
[match_index
] + value
[edit_end
:] 
3980 ##                dbg('setting value to "%s"' % newvalue) 
3981                 self
._SetValue
(newvalue
) 
3982                 self
._SetInsertionPoint
(min(edit_end
, len(newvalue
.rstrip()))) 
3983                 self
._OnAutoSelect
(field
, match_index
) 
3984                 self
._CheckValid
()  # recolor as appopriate 
3987         if keycode 
in (wx
.WXK_UP
, wx
.WXK_DOWN
, wx
.WXK_LEFT
, wx
.WXK_RIGHT
): 
3988             # treat as left right arrow if unshifted, tab/shift tab if shifted. 
3989             if event
.ShiftDown(): 
3990                 if keycode 
in (wx
.WXK_DOWN
, wx
.WXK_RIGHT
): 
3991                     # remove "shifting" and treat as (forward) tab: 
3992                     event
.m_shiftDown 
= False 
3993                 keep_processing 
= self
._OnChangeField
(event
) 
3995                 keep_processing 
= self
._OnArrow
(event
) 
3996         # else some other key; keep processing the key 
3998 ##        dbg('keep processing?', keep_processing, indent=0) 
3999         return keep_processing
 
4002     def _OnAutoSelect(self
, field
, match_index 
= None): 
4004         Function called if autoselect feature is enabled and entire control 
4007 ##        dbg('MaskedEditMixin::OnAutoSelect', field._index) 
4008         if match_index 
is not None: 
4009             field
._autoCompleteIndex 
= match_index
 
4012     def _autoComplete(self
, direction
, choices
, value
, compareNoCase
, current_index
): 
4014         This function gets called in response to Auto-complete events. 
4015         It attempts to find a match to the specified value against the 
4016         list of choices; if exact match, the index of then next 
4017         appropriate value in the list, based on the given direction. 
4018         If not an exact match, it will return the index of the 1st value from 
4019         the choice list for which the partial value can be extended to match. 
4020         If no match found, it will return None. 
4021         The function returns a 2-tuple, with the 2nd element being a boolean 
4022         that indicates if partial match was necessary. 
4024 ##        dbg('autoComplete(direction=', direction, 'choices=',choices, 'value=',value,'compareNoCase?', compareNoCase, 'current_index:', current_index, indent=1) 
4026 ##            dbg('nothing to match against', indent=0) 
4027             return (None, False) 
4029         partial_match 
= False 
4032             value 
= value
.lower() 
4034         last_index 
= len(choices
) - 1 
4035         if value 
in choices
: 
4036 ##            dbg('"%s" in', choices) 
4037             if current_index 
is not None and choices
[current_index
] == value
: 
4038                 index 
= current_index
 
4040                 index 
= choices
.index(value
) 
4042 ##            dbg('matched "%s" (%d)' % (choices[index], index)) 
4044 ##                dbg('going to previous') 
4045                 if index 
== 0: index 
= len(choices
) - 1 
4048                 if index 
== len(choices
) - 1: index 
= 0 
4050 ##            dbg('change value to "%s" (%d)' % (choices[index], index)) 
4053             partial_match 
= True 
4054             value 
= value
.strip() 
4055 ##            dbg('no match; try to auto-complete:') 
4057 ##            dbg('searching for "%s"' % value) 
4058             if current_index 
is None: 
4059                 indices 
= range(len(choices
)) 
4064                     indices 
= range(current_index 
+1, len(choices
)) + range(current_index
+1) 
4065 ##                    dbg('range(current_index+1 (%d), len(choices) (%d)) + range(%d):' % (current_index+1, len(choices), current_index+1), indices) 
4067                     indices 
= range(current_index
-1, -1, -1) + range(len(choices
)-1, current_index
-1, -1) 
4068 ##                    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) 
4069 ####            dbg('indices:', indices) 
4070             for index 
in indices
: 
4071                 choice 
= choices
[index
] 
4072                 if choice
.find(value
, 0) == 0: 
4073 ##                    dbg('match found:', choice) 
4077 ##                    dbg('choice: "%s" - no match' % choice) 
4079             if match 
is not None: 
4080 ##                dbg('matched', match) 
4083 ##                dbg('no match found') 
4086         return (match
, partial_match
) 
4089     def _AdjustField(self
, pos
): 
4091         This function gets called by default whenever the cursor leaves a field. 
4092         The pos argument given is the char position before leaving that field. 
4093         By default, floating point, integer and date values are adjusted to be 
4094         legal in this function.  Derived classes may override this function 
4095         to modify the value of the control in a different way when changing fields. 
4097         NOTE: these change the value immediately, and restore the cursor to 
4098         the passed location, so that any subsequent code can then move it 
4099         based on the operation being performed. 
4101         newvalue 
= value 
= self
._GetValue
() 
4102         field 
= self
._FindField
(pos
) 
4103         start
, end
, slice = self
._FindFieldExtent
(getslice
=True) 
4104         newfield 
= field
._AdjustField
(slice) 
4105         newvalue 
= value
[:start
] + newfield 
+ value
[end
:] 
4107         if self
._isFloat 
and newvalue 
!= self
._template
: 
4108             newvalue 
= self
._adjustFloat
(newvalue
) 
4110         if self
._ctrl
_constraints
._isInt 
and value 
!= self
._template
: 
4111             newvalue 
= self
._adjustInt
(value
) 
4113         if self
._isDate 
and value 
!= self
._template
: 
4114             newvalue 
= self
._adjustDate
(value
, fixcentury
=True) 
4115             if self
._4digityear
: 
4116                 year2dig 
= self
._dateExtent 
- 2 
4117                 if pos 
== year2dig 
and value
[year2dig
] != newvalue
[year2dig
]: 
4120         if newvalue 
!= value
: 
4121 ##            dbg('old value: "%s"\nnew value: "%s"' % (value, newvalue)) 
4122             self
._SetValue
(newvalue
) 
4123             self
._SetInsertionPoint
(pos
) 
4126     def _adjustKey(self
, pos
, key
): 
4127         """ Apply control formatting to the key (e.g. convert to upper etc). """ 
4128         field 
= self
._FindField
(pos
) 
4129         if field
._forceupper 
and key 
in range(97,123): 
4130             key 
= ord( chr(key
).upper()) 
4132         if field
._forcelower 
and key 
in range(97,123): 
4133             key 
= ord( chr(key
).lower()) 
4138     def _adjustPos(self
, pos
, key
): 
4140         Checks the current insertion point position and adjusts it if 
4141         necessary to skip over non-editable characters. 
4143 ##        dbg('_adjustPos', pos, key, indent=1) 
4144         sel_start
, sel_to 
= self
._GetSelection
() 
4145         # If a numeric or decimal mask, and negatives allowed, reserve the 
4146         # first space for sign, and last one if using parens. 
4148             and ((pos 
== self
._signpos 
and key 
in (ord('-'), ord('+'), ord(' ')) ) 
4149                  or (self
._useParens 
and pos 
== self
._masklength 
-1))): 
4150 ##            dbg('adjusted pos:', pos, indent=0) 
4153         if key 
not in self
._nav
: 
4154             field 
= self
._FindField
(pos
) 
4156 ##            dbg('field._insertRight?', field._insertRight) 
4157 ##            if self._signOk: dbg('self._signpos:', self._signpos) 
4158             if field
._insertRight
:              # if allow right-insert 
4159                 start
, end 
= field
._extent
 
4160                 slice = self
._GetValue
()[start
:end
].strip() 
4161                 field_len 
= end 
- start
 
4162                 if pos 
== end
:                      # if cursor at right edge of field 
4163                     # if not filled or supposed to stay in field, keep current position 
4164 ####                    dbg('pos==end') 
4165 ####                    dbg('len (slice):', len(slice)) 
4166 ####                    dbg('field_len?', field_len) 
4167 ####                    dbg('pos==end; len (slice) < field_len?', len(slice) < field_len) 
4168 ####                    dbg('not field._moveOnFieldFull?', not field._moveOnFieldFull) 
4169                     if len(slice) == field_len 
and field
._moveOnFieldFull
: 
4170                         # move cursor to next field: 
4171                         pos 
= self
._findNextEntry
(pos
) 
4172                         self
._SetInsertionPoint
(pos
) 
4174                             self
._SetSelection
(pos
, sel_to
)     # restore selection 
4176                             self
._SetSelection
(pos
, pos
)        # remove selection 
4177                     else: # leave cursor alone 
4180                     # if at start of control, move to right edge 
4181                     if (sel_to 
== sel_start
 
4182                         and (self
._isTemplateChar
(pos
) or (pos 
== start 
and len(slice)+ 1 < field_len
)) 
4184                         pos 
= end                   
# move to right edge 
4185 ##                    elif sel_start <= start and sel_to == end: 
4186 ##                        # select to right edge of field - 1 (to replace char) 
4188 ##                        self._SetInsertionPoint(pos) 
4189 ##                        # restore selection 
4190 ##                        self._SetSelection(sel_start, pos) 
4192                     # if selected to beginning and signed, and not changing sign explicitly: 
4193                     elif self
._signOk 
and sel_start 
== 0 and key 
not in (ord('-'), ord('+'), ord(' ')): 
4194                         # adjust to past reserved sign position: 
4195                         pos 
= self
._fields
[0]._extent
[0] 
4196 ##                        dbg('adjusting field to ', pos) 
4197                         self
._SetInsertionPoint
(pos
) 
4198                         # but keep original selection, to allow replacement of any sign:  
4199                         self
._SetSelection
(0, sel_to
) 
4201                         pass    # leave position/selection alone 
4203             # else make sure the user is not trying to type over a template character 
4204             # If they are, move them to the next valid entry position 
4205             elif self
._isTemplateChar
(pos
): 
4206                 if( not field
._moveOnFieldFull
 
4207                       and (not self
._signOk
 
4209                                and field
._index 
== 0 
4210                                and pos 
> 0) ) ):      # don't move to next field without explicit cursor movement 
4213                     # find next valid position 
4214                     pos 
= self
._findNextEntry
(pos
) 
4215                     self
._SetInsertionPoint
(pos
) 
4216                     if pos 
< sel_to
:    # restore selection 
4217                         self
._SetSelection
(pos
, sel_to
) 
4219                         self
._SetSelection
(pos
, pos
) 
4220 ##        dbg('adjusted pos:', pos, indent=0) 
4224     def _adjustFloat(self
, candidate
=None): 
4226         'Fixes' an floating point control. Collapses spaces, right-justifies, etc. 
4228 ##        dbg('MaskedEditMixin::_adjustFloat, candidate = "%s"' % candidate, indent=1) 
4229         lenInt
,lenFraction  
= [len(s
) for s 
in self
._mask
.split('.')]  ## Get integer, fraction lengths 
4231         if candidate 
is None: value 
= self
._GetValue
() 
4232         else: value 
= candidate
 
4233 ##        dbg('value = "%(value)s"' % locals(), 'len(value):', len(value)) 
4234         intStr
, fracStr 
= value
.split(self
._decimalChar
) 
4236         intStr 
= self
._fields
[0]._AdjustField
(intStr
) 
4237 ##        dbg('adjusted intStr: "%s"' % intStr) 
4238         lenInt 
= len(intStr
) 
4239         fracStr 
= fracStr 
+ ('0'*(lenFraction
-len(fracStr
)))  # add trailing spaces to decimal 
4241 ##        dbg('intStr "%(intStr)s"' % locals()) 
4242 ##        dbg('lenInt:', lenInt) 
4244         intStr 
= string
.rjust( intStr
[-lenInt
:], lenInt
) 
4245 ##        dbg('right-justifed intStr = "%(intStr)s"' % locals()) 
4246         newvalue 
= intStr 
+ self
._decimalChar 
+ fracStr
 
4249             if len(newvalue
) < self
._masklength
: 
4250                 newvalue 
= ' ' + newvalue
 
4251             signedvalue 
= self
._getSignedValue
(newvalue
)[0] 
4252             if signedvalue 
is not None: newvalue 
= signedvalue
 
4254         # Finally, align string with decimal position, left-padding with 
4256         newdecpos 
= newvalue
.find(self
._decimalChar
) 
4257         if newdecpos 
< self
._decimalpos
: 
4258             padlen 
= self
._decimalpos 
- newdecpos
 
4259             newvalue 
= string
.join([' ' * padlen
] + [newvalue
] ,'') 
4261         if self
._signOk 
and self
._useParens
: 
4262             if newvalue
.find('(') != -1: 
4263                 newvalue 
= newvalue
[:-1] + ')' 
4265                 newvalue 
= newvalue
[:-1] + ' ' 
4267 ##        dbg('newvalue = "%s"' % newvalue) 
4268         if candidate 
is None: 
4269             wx
.CallAfter(self
._SetValue
, newvalue
) 
4274     def _adjustInt(self
, candidate
=None): 
4275         """ 'Fixes' an integer control. Collapses spaces, right or left-justifies.""" 
4276 ##        dbg("MaskedEditMixin::_adjustInt", candidate) 
4277         lenInt 
= self
._masklength
 
4278         if candidate 
is None: value 
= self
._GetValue
() 
4279         else: value 
= candidate
 
4281         intStr 
= self
._fields
[0]._AdjustField
(value
) 
4282         intStr 
= intStr
.strip() # drop extra spaces 
4283 ##        dbg('adjusted field: "%s"' % intStr) 
4285         if self
._isNeg 
and intStr
.find('-') == -1 and intStr
.find('(') == -1: 
4287                 intStr 
= '(' + intStr 
+ ')' 
4289                 intStr 
= '-' + intStr
 
4290         elif self
._isNeg 
and intStr
.find('-') != -1 and self
._useParens
: 
4291             intStr 
= intStr
.replace('-', '(') 
4293         if( self
._signOk 
and ((self
._useParens 
and intStr
.find('(') == -1) 
4294                                 or (not self
._useParens 
and intStr
.find('-') == -1))): 
4295             intStr 
= ' ' + intStr
 
4297                 intStr 
+= ' '   # space for right paren position 
4299         elif self
._signOk 
and self
._useParens 
and intStr
.find('(') != -1 and intStr
.find(')') == -1: 
4300             # ensure closing right paren: 
4303         if self
._fields
[0]._alignRight
:     ## Only if right-alignment is enabled 
4304             intStr 
= intStr
.rjust( lenInt 
) 
4306             intStr 
= intStr
.ljust( lenInt 
) 
4308         if candidate 
is None: 
4309             wx
.CallAfter(self
._SetValue
, intStr 
) 
4313     def _adjustDate(self
, candidate
=None, fixcentury
=False, force4digit_year
=False): 
4315         'Fixes' a date control, expanding the year if it can. 
4316         Applies various self-formatting options. 
4318 ##        dbg("MaskedEditMixin::_adjustDate", indent=1) 
4319         if candidate 
is None: text    
= self
._GetValue
() 
4320         else: text 
= candidate
 
4321 ##        dbg('text=', text) 
4322         if self
._datestyle 
== "YMD": 
4327 ##        dbg('getYear: "%s"' % _getYear(text, self._datestyle)) 
4328         year    
= string
.replace( _getYear( text
, self
._datestyle
),self
._fields
[year_field
]._fillChar
,"")  # drop extra fillChars 
4329         month   
= _getMonth( text
, self
._datestyle
) 
4330         day     
= _getDay( text
, self
._datestyle
) 
4331 ##        dbg('self._datestyle:', self._datestyle, 'year:', year, 'Month', month, 'day:', day) 
4334         yearstart 
= self
._dateExtent 
- 4 
4338                  or (self
._GetInsertionPoint
() > yearstart
+1 and text
[yearstart
+2] == ' ') 
4339                  or (self
._GetInsertionPoint
() > yearstart
+2 and text
[yearstart
+3] == ' ') ) ): 
4340             ## user entered less than four digits and changing fields or past point where we could 
4341             ## enter another digit: 
4345 ##                dbg('bad year=', year) 
4346                 year 
= text
[yearstart
:self
._dateExtent
] 
4348         if len(year
) < 4 and yearVal
: 
4350                 # Fix year adjustment to be less "20th century" :-) and to adjust heuristic as the 
4352                 now 
= wx
.DateTime_Now() 
4353                 century 
= (now
.GetYear() /100) * 100        # "this century" 
4354                 twodig_year 
= now
.GetYear() - century       
# "this year" (2 digits) 
4355                 # if separation between today's 2-digit year and typed value > 50, 
4356                 #      assume last century, 
4357                 # else assume this century. 
4359                 # Eg: if 2003 and yearVal == 30, => 2030 
4360                 #     if 2055 and yearVal == 80, => 2080 
4361                 #     if 2010 and yearVal == 96, => 1996 
4363                 if abs(yearVal 
- twodig_year
) > 50: 
4364                     yearVal 
= (century 
- 100) + yearVal
 
4366                     yearVal 
= century 
+ yearVal
 
4367                 year 
= str( yearVal 
) 
4368             else:   # pad with 0's to make a 4-digit year 
4369                 year 
= "%04d" % yearVal
 
4370             if self
._4digityear 
or force4digit_year
: 
4371                 text 
= _makeDate(year
, month
, day
, self
._datestyle
, text
) + text
[self
._dateExtent
:] 
4372 ##        dbg('newdate: "%s"' % text, indent=0) 
4376     def _goEnd(self
, getPosOnly
=False): 
4377         """ Moves the insertion point to the end of user-entry """ 
4378 ##        dbg("MaskedEditMixin::_goEnd; getPosOnly:", getPosOnly, indent=1) 
4379         text 
= self
._GetValue
() 
4380 ####        dbg('text: "%s"' % text) 
4382         if len(text
.rstrip()): 
4383             for i 
in range( min( self
._masklength
-1, len(text
.rstrip())), -1, -1): 
4384 ####                dbg('i:', i, 'self._isMaskChar(%d)' % i, self._isMaskChar(i)) 
4385                 if self
._isMaskChar
(i
): 
4387 ####                    dbg("text[%d]: '%s'" % (i, char)) 
4393             pos 
= self
._goHome
(getPosOnly
=True) 
4395             pos 
= min(i
,self
._masklength
) 
4397         field 
= self
._FindField
(pos
) 
4398         start
, end 
= field
._extent
 
4399         if field
._insertRight 
and pos 
< end
: 
4401 ##        dbg('next pos:', pos) 
4406             self
._SetInsertionPoint
(pos
) 
4409     def _goHome(self
, getPosOnly
=False): 
4410         """ Moves the insertion point to the beginning of user-entry """ 
4411 ##        dbg("MaskedEditMixin::_goHome; getPosOnly:", getPosOnly, indent=1) 
4412         text 
= self
._GetValue
() 
4413         for i 
in range(self
._masklength
): 
4414             if self
._isMaskChar
(i
): 
4421             self
._SetInsertionPoint
(max(i
,0)) 
4425     def _getAllowedChars(self
, pos
): 
4426         """ Returns a string of all allowed user input characters for the provided 
4427             mask character plus control options 
4429         maskChar 
= self
.maskdict
[pos
] 
4430         okchars 
= self
.maskchardict
[maskChar
]    ## entry, get mask approved characters 
4432         # convert okchars to unicode if required; will force subsequent appendings to 
4433         # result in unicode strings 
4434         if 'unicode' in wx
.PlatformInfo 
and type(okchars
) != types
.UnicodeType
: 
4435             okchars 
= okchars
.decode(self
._defaultEncoding
) 
4437         field 
= self
._FindField
(pos
) 
4438         if okchars 
and field
._okSpaces
:          ## Allow spaces? 
4440         if okchars 
and field
._includeChars
:      ## any additional included characters? 
4441             okchars 
+= field
._includeChars
 
4442 ####        dbg('okchars[%d]:' % pos, okchars) 
4446     def _isMaskChar(self
, pos
): 
4447         """ Returns True if the char at position pos is a special mask character (e.g. NCXaA#) 
4449         if pos 
< self
._masklength
: 
4450             return self
.ismasked
[pos
] 
4455     def _isTemplateChar(self
,Pos
): 
4456         """ Returns True if the char at position pos is a template character (e.g. -not- NCXaA#) 
4458         if Pos 
< self
._masklength
: 
4459             return not self
._isMaskChar
(Pos
) 
4464     def _isCharAllowed(self
, char
, pos
, checkRegex
=False, allowAutoSelect
=True, ignoreInsertRight
=False): 
4465         """ Returns True if character is allowed at the specific position, otherwise False.""" 
4466 ##        dbg('_isCharAllowed', char, pos, checkRegex, indent=1) 
4467         field 
= self
._FindField
(pos
) 
4468         right_insert 
= False 
4470         if self
.controlInitialized
: 
4471             sel_start
, sel_to 
= self
._GetSelection
() 
4473             sel_start
, sel_to 
= pos
, pos
 
4475         if (field
._insertRight 
or self
._ctrl
_constraints
._insertRight
) and not ignoreInsertRight
: 
4476             start
, end 
= field
._extent
 
4477             field_len 
= end 
- start
 
4478             if self
.controlInitialized
: 
4479                 value 
= self
._GetValue
() 
4480                 fstr 
= value
[start
:end
].strip() 
4482                     while fstr 
and fstr
[0] == '0': 
4484                 input_len 
= len(fstr
) 
4485                 if self
._signOk 
and '-' in fstr 
or '(' in fstr
: 
4486                     input_len 
-= 1  # sign can move out of field, so don't consider it in length 
4488                 value 
= self
._template
 
4489                 input_len 
= 0   # can't get the current "value", so use 0 
4492             # if entire field is selected or position is at end and field is not full, 
4493             # or if allowed to right-insert at any point in field and field is not full and cursor is not at a fillChar 
4494             # or the field is a singleton integer field and is currently 0 and we're at the end: 
4495             if( (sel_start
, sel_to
) == field
._extent
 
4496                 or (pos 
== end 
and ((input_len 
< field_len
) 
4498                                          and input_len 
== field_len
 
4500                                          and value
[end
-1] == '0' 
4504 ##                dbg('pos = end - 1 = ', pos, 'right_insert? 1') 
4506             elif( field
._allowInsert 
and sel_start 
== sel_to
 
4507                   and (sel_to 
== end 
or (sel_to 
< self
._masklength 
and value
[sel_start
] != field
._fillChar
)) 
4508                   and input_len 
< field_len 
): 
4509                 pos 
= sel_to 
- 1    # where character will go 
4510 ##                dbg('pos = sel_to - 1 = ', pos, 'right_insert? 1') 
4512             # else leave pos alone... 
4514 ##                dbg('pos stays ', pos, 'right_insert? 0') 
4517         if self
._isTemplateChar
( pos 
):  ## if a template character, return empty 
4518 ##            dbg('%d is a template character; returning False' % pos, indent=0) 
4521         if self
._isMaskChar
( pos 
): 
4522             okChars  
= self
._getAllowedChars
(pos
) 
4524             if self
._fields
[0]._groupdigits 
and (self
._isInt 
or (self
._isFloat 
and pos 
< self
._decimalpos
)): 
4525                 okChars 
+= self
._fields
[0]._groupChar
 
4528                 if self
._isInt 
or (self
._isFloat 
and pos 
< self
._decimalpos
): 
4532                 elif self
._useParens 
and (self
._isInt 
or (self
._isFloat 
and pos 
> self
._decimalpos
)): 
4535 ####            dbg('%s in %s?' % (char, okChars), char in okChars) 
4536             approved 
= char 
in okChars
 
4538             if approved 
and checkRegex
: 
4539 ##                dbg("checking appropriate regex's") 
4540                 value 
= self
._eraseSelection
(self
._GetValue
()) 
4542                     # move the position to the right side of the insertion: 
4547                     newvalue
, ignore
, ignore
, ignore
, ignore 
= self
._insertKey
(char
, at
, sel_start
, sel_to
, value
, allowAutoSelect
=True) 
4549                     newvalue
, ignore 
= self
._insertKey
(char
, at
, sel_start
, sel_to
, value
) 
4550 ##                dbg('newvalue: "%s"' % newvalue) 
4552                 fields 
= [self
._FindField
(pos
)] + [self
._ctrl
_constraints
] 
4553                 for field 
in fields
:    # includes fields[-1] == "ctrl_constraints" 
4554                     if field
._regexMask 
and field
._filter
: 
4555 ##                        dbg('checking vs. regex') 
4556                         start
, end 
= field
._extent
 
4557                         slice = newvalue
[start
:end
] 
4558                         approved 
= (re
.match( field
._filter
, slice) is not None) 
4559 ##                        dbg('approved?', approved) 
4560                     if not approved
: break 
4564 ##            dbg('%d is a !???! character; returning False', indent=0) 
4568     def _applyFormatting(self
): 
4569         """ Apply formatting depending on the control's state. 
4570             Need to find a way to call this whenever the value changes, in case the control's 
4571             value has been changed or set programatically. 
4574 ##        dbg('MaskedEditMixin::_applyFormatting', indent=1) 
4576         # Handle negative numbers 
4578             text
, signpos
, right_signpos 
= self
._getSignedValue
() 
4579 ##            dbg('text: "%s", signpos:' % text, signpos) 
4580             if text 
and signpos 
!= self
._signpos
: 
4581                 self
._signpos 
= signpos
 
4582             if not text 
or text
[signpos
] not in ('-','('): 
4584 ##                dbg('no valid sign found; new sign:', self._isNeg) 
4585             elif text 
and self
._valid 
and not self
._isNeg 
and text
[signpos
] in ('-', '('): 
4586 ##                dbg('setting _isNeg to True') 
4588 ##            dbg('self._isNeg:', self._isNeg) 
4590         if self
._signOk 
and self
._isNeg
: 
4591             fc 
= self
._signedForegroundColour
 
4593             fc 
= self
._foregroundColour
 
4595         if hasattr(fc
, '_name'): 
4599 ##        dbg('setting foreground to', c) 
4600         self
.SetForegroundColour(fc
) 
4605                 bc 
= self
._emptyBackgroundColour
 
4607                 bc 
= self
._validBackgroundColour
 
4610             bc 
= self
._invalidBackgroundColour
 
4611         if hasattr(bc
, '_name'): 
4615 ##        dbg('setting background to', c) 
4616         self
.SetBackgroundColour(bc
) 
4618 ##        dbg(indent=0, suspend=0) 
4621     def _getAbsValue(self
, candidate
=None): 
4622         """ Return an unsigned value (i.e. strip the '-' prefix if any), and sign position(s). 
4624 ##        dbg('MaskedEditMixin::_getAbsValue; candidate="%s"' % candidate, indent=1) 
4625         if candidate 
is None: text 
= self
._GetValue
() 
4626         else: text 
= candidate
 
4627         right_signpos 
= text
.find(')') 
4630             if self
._ctrl
_constraints
._alignRight 
and self
._fields
[0]._fillChar 
== ' ': 
4631                 signpos 
= text
.find('-') 
4633 ##                    dbg('no - found; searching for (') 
4634                     signpos 
= text
.find('(') 
4636 ##                    dbg('- found at', signpos) 
4640 ##                    dbg('signpos still -1') 
4641 ##                    dbg('len(%s) (%d) < len(%s) (%d)?' % (text, len(text), self._mask, self._masklength), len(text) < self._masklength) 
4642                     if len(text
) < self
._masklength
: 
4644                     if len(text
) < self
._masklength
: 
4646                     if len(text
) > self
._masklength 
and text
[-1] in (')', ' '): 
4649 ##                        dbg('len(%s) (%d), len(%s) (%d)' % (text, len(text), self._mask, self._masklength)) 
4650 ##                        dbg('len(%s) - (len(%s) + 1):' % (text, text.lstrip()) , len(text) - (len(text.lstrip()) + 1)) 
4651                         signpos 
= len(text
) - (len(text
.lstrip()) + 1) 
4653                         if self
._useParens 
and not text
.strip(): 
4654                             signpos 
-= 1    # empty value; use penultimate space 
4655 ##                dbg('signpos:', signpos) 
4657                     text 
= text
[:signpos
] + ' ' + text
[signpos
+1:] 
4662                     text 
= self
._template
[0] + text
[1:] 
4666             if right_signpos 
!= -1: 
4668                     text 
= text
[:right_signpos
] + ' ' + text
[right_signpos
+1:] 
4669                 elif len(text
) > self
._masklength
: 
4670                     text 
= text
[:right_signpos
] + text
[right_signpos
+1:] 
4674             elif self
._useParens 
and self
._signOk
: 
4675                 # figure out where it ought to go: 
4676                 right_signpos 
= self
._masklength 
- 1     # initial guess 
4677                 if not self
._ctrl
_constraints
._alignRight
: 
4678 ##                    dbg('not right-aligned') 
4679                     if len(text
.strip()) == 0: 
4680                         right_signpos 
= signpos 
+ 1 
4681                     elif len(text
.strip()) < self
._masklength
: 
4682                         right_signpos 
= len(text
.rstrip()) 
4683 ##                dbg('right_signpos:', right_signpos) 
4685             groupchar 
= self
._fields
[0]._groupChar
 
4687                 value 
= long(text
.replace(groupchar
,'').replace('(','-').replace(')','').replace(' ', '')) 
4689 ##                dbg('invalid number', indent=0) 
4690                 return None, signpos
, right_signpos
 
4694                 groupchar 
= self
._fields
[0]._groupChar
 
4695                 value 
= float(text
.replace(groupchar
,'').replace(self
._decimalChar
, '.').replace('(', '-').replace(')','').replace(' ', '')) 
4696 ##                dbg('value:', value) 
4700             if value 
< 0 and value 
is not None: 
4701                 signpos 
= text
.find('-') 
4703                     signpos 
= text
.find('(') 
4705                 text 
= text
[:signpos
] + self
._template
[signpos
] + text
[signpos
+1:] 
4707                 # look forwards up to the decimal point for the 1st non-digit 
4708 ##                dbg('decimal pos:', self._decimalpos) 
4709 ##                dbg('text: "%s"' % text) 
4711                     signpos 
= self
._decimalpos 
- (len(text
[:self
._decimalpos
].lstrip()) + 1) 
4712                     # prevent checking for empty string - Tomo - Wed 14 Jan 2004 03:19:09 PM CET 
4713                     if len(text
) >= signpos
+1 and  text
[signpos
+1] in ('-','('): 
4717 ##                dbg('signpos:', signpos) 
4721                     right_signpos 
= self
._masklength 
- 1 
4722                     text 
= text
[:right_signpos
] + ' ' 
4723                     if text
[signpos
] == '(': 
4724                         text 
= text
[:signpos
] + ' ' + text
[signpos
+1:] 
4726                     right_signpos 
= text
.find(')') 
4727                     if right_signpos 
!= -1: 
4732 ##                dbg('invalid number') 
4735 ##        dbg('abstext = "%s"' % text, 'signpos:', signpos, 'right_signpos:', right_signpos) 
4737         return text
, signpos
, right_signpos
 
4740     def _getSignedValue(self
, candidate
=None): 
4741         """ Return a signed value by adding a "-" prefix if the value 
4742             is set to negative, or a space if positive. 
4744 ##        dbg('MaskedEditMixin::_getSignedValue; candidate="%s"' % candidate, indent=1) 
4745         if candidate 
is None: text 
= self
._GetValue
() 
4746         else: text 
= candidate
 
4749         abstext
, signpos
, right_signpos 
= self
._getAbsValue
(text
) 
4753                 return abstext
, signpos
, right_signpos
 
4755             if self
._isNeg 
or text
[signpos
] in ('-', '('): 
4762             if abstext
[signpos
] not in string
.digits
: 
4763                 text 
= abstext
[:signpos
] + sign 
+ abstext
[signpos
+1:] 
4765                 # this can happen if value passed is too big; sign assumed to be 
4766                 # in position 0, but if already filled with a digit, prepend sign... 
4767                 text 
= sign 
+ abstext
 
4768             if self
._useParens 
and text
.find('(') != -1: 
4769                 text 
= text
[:right_signpos
] + ')' + text
[right_signpos
+1:] 
4772 ##        dbg('signedtext = "%s"' % text, 'signpos:', signpos, 'right_signpos', right_signpos) 
4774         return text
, signpos
, right_signpos
 
4777     def GetPlainValue(self
, candidate
=None): 
4778         """ Returns control's value stripped of the template text. 
4779             plainvalue = MaskedEditMixin.GetPlainValue() 
4781 ##        dbg('MaskedEditMixin::GetPlainValue; candidate="%s"' % candidate, indent=1) 
4783         if candidate 
is None: text 
= self
._GetValue
() 
4784         else: text 
= candidate
 
4787 ##            dbg('returned ""', indent=0) 
4791             for idx 
in range( min(len(self
._template
), len(text
)) ): 
4792                 if self
._mask
[idx
] in maskchars
: 
4795             if self
._isFloat 
or self
._isInt
: 
4796 ##                dbg('plain so far: "%s"' % plain) 
4797                 plain 
= plain
.replace('(', '-').replace(')', ' ') 
4798 ##                dbg('plain after sign regularization: "%s"' % plain) 
4800                 if self
._signOk 
and self
._isNeg 
and plain
.count('-') == 0: 
4801                     # must be in reserved position; add to "plain value" 
4802                     plain 
= '-' + plain
.strip() 
4804                 if self
._fields
[0]._alignRight
: 
4805                     lpad 
= plain
.count(',') 
4806                     plain 
= ' ' * lpad 
+ plain
.replace(',','') 
4808                     plain 
= plain
.replace(',','') 
4809 ##                dbg('plain after pad and group:"%s"' % plain) 
4811 ##            dbg('returned "%s"' % plain.rstrip(), indent=0) 
4812             return plain
.rstrip() 
4815     def IsEmpty(self
, value
=None): 
4817         Returns True if control is equal to an empty value. 
4818         (Empty means all editable positions in the template == fillChar.) 
4820         if value 
is None: value 
= self
._GetValue
() 
4821         if value 
== self
._template 
and not self
._defaultValue
: 
4822 ####            dbg("IsEmpty? 1 (value == self._template and not self._defaultValue)") 
4823             return True     # (all mask chars == fillChar by defn) 
4824         elif value 
== self
._template
: 
4826             for pos 
in range(len(self
._template
)): 
4827 ####                dbg('isMaskChar(%(pos)d)?' % locals(), self._isMaskChar(pos)) 
4828 ####                dbg('value[%(pos)d] != self._fillChar?' %locals(), value[pos] != self._fillChar[pos]) 
4829                 if self
._isMaskChar
(pos
) and value
[pos
] not in (' ', self
._fillChar
[pos
]): 
4831 ####            dbg("IsEmpty? %(empty)d (do all mask chars == fillChar?)" % locals()) 
4834 ####            dbg("IsEmpty? 0 (value doesn't match template)") 
4838     def IsDefault(self
, value
=None): 
4840         Returns True if the value specified (or the value of the control if not specified) 
4841         is equal to the default value. 
4843         if value 
is None: value 
= self
._GetValue
() 
4844         return value 
== self
._template
 
4847     def IsValid(self
, value
=None): 
4848         """ Indicates whether the value specified (or the current value of the control 
4849         if not specified) is considered valid.""" 
4850 ####        dbg('MaskedEditMixin::IsValid("%s")' % value, indent=1) 
4851         if value 
is None: value 
= self
._GetValue
() 
4852         ret 
= self
._CheckValid
(value
) 
4857     def _eraseSelection(self
, value
=None, sel_start
=None, sel_to
=None): 
4858         """ Used to blank the selection when inserting a new character. """ 
4859 ##        dbg("MaskedEditMixin::_eraseSelection", indent=1) 
4860         if value 
is None: value 
= self
._GetValue
() 
4861         if sel_start 
is None or sel_to 
is None: 
4862             sel_start
, sel_to 
= self
._GetSelection
()                   ## check for a range of selected text 
4863 ##        dbg('value: "%s"' % value) 
4864 ##        dbg("current sel_start, sel_to:", sel_start, sel_to) 
4866         newvalue 
= list(value
) 
4867         for i 
in range(sel_start
, sel_to
): 
4868             if self
._signOk 
and newvalue
[i
] in ('-', '(', ')'): 
4869 ##                dbg('found sign (%s) at' % newvalue[i], i) 
4871                 # balance parentheses: 
4872                 if newvalue
[i
] == '(': 
4873                     right_signpos 
= value
.find(')') 
4874                     if right_signpos 
!= -1: 
4875                         newvalue
[right_signpos
] = ' ' 
4877                 elif newvalue
[i
] == ')': 
4878                     left_signpos 
= value
.find('(') 
4879                     if left_signpos 
!= -1: 
4880                         newvalue
[left_signpos
] = ' ' 
4884             elif self
._isMaskChar
(i
): 
4885                 field 
= self
._FindField
(i
) 
4889                     newvalue
[i
] = self
._template
[i
] 
4891         value 
= string
.join(newvalue
,"") 
4892 ##        dbg('new value: "%s"' % value) 
4897     def _insertKey(self
, char
, pos
, sel_start
, sel_to
, value
, allowAutoSelect
=False): 
4898         """ Handles replacement of the character at the current insertion point.""" 
4899 ##        dbg('MaskedEditMixin::_insertKey', "\'" + char + "\'", pos, sel_start, sel_to, '"%s"' % value, indent=1) 
4901         text 
= self
._eraseSelection
(value
) 
4902         field 
= self
._FindField
(pos
) 
4903         start
, end 
= field
._extent
 
4907         # if >= 2 chars selected in a right-insert field, do appropriate erase on field, 
4908         # then set selection to end, and do usual right insert. 
4909         if sel_start 
!= sel_to 
and sel_to 
>= sel_start
+2: 
4910             field 
= self
._FindField
(sel_start
) 
4911             if( field
._insertRight                          
# if right-insert 
4912                 and field
._allowInsert                      
# and allow insert at any point in field 
4913                 and field 
== self
._FindField
(sel_to
) ):     # and selection all in same field 
4914                 text 
= self
._OnErase
(just_return_value
=True)    # remove selection before insert 
4915 ##                dbg('text after (left)erase: "%s"' % text) 
4916                 pos 
= sel_start 
= sel_to
 
4918         if pos 
!= sel_start 
and sel_start 
== sel_to
: 
4919             # adjustpos must have moved the position; make selection match: 
4920             sel_start 
= sel_to 
= pos
 
4922 ##        dbg('field._insertRight?', field._insertRight) 
4923 ##        dbg('field._allowInsert?', field._allowInsert) 
4924 ##        dbg('sel_start, end', sel_start, end) 
4926 ##            dbg('text[sel_start] != field._fillChar?', text[sel_start] != field._fillChar) 
4929         if( field
._insertRight                                  
# field allows right insert 
4930             and ((sel_start
, sel_to
) == field
._extent           
# and whole field selected 
4931                  or (sel_start 
== sel_to                        
# or nothing selected 
4932                      and (sel_start 
== end                      
# and cursor at right edge 
4933                           or (field
._allowInsert                
# or field allows right-insert 
4934                               and sel_start 
< end               
# next to other char in field: 
4935                               and text
[sel_start
] != field
._fillChar
) ) ) ) ): 
4936 ##            dbg('insertRight') 
4937             fstr 
= text
[start
:end
] 
4938             erasable_chars 
= [field
._fillChar
, ' '] 
4940             # if zero padding field, or a single digit, and currently a value of 0, allow erasure of 0: 
4941             if field
._padZero 
or (field
._isInt 
and (end 
- start 
== 1) and fstr
[0] == '0'): 
4942                 erasable_chars
.append('0') 
4945 ####            dbg("fstr[0]:'%s'" % fstr[0]) 
4946 ####            dbg('field_index:', field._index) 
4947 ####            dbg("fstr[0] in erasable_chars?", fstr[0] in erasable_chars) 
4948 ####            dbg("self._signOk and field._index == 0 and fstr[0] in ('-','(')?", self._signOk and field._index == 0 and fstr[0] in ('-','(')) 
4949             if fstr
[0] in erasable_chars 
or (self
._signOk 
and field
._index 
== 0 and fstr
[0] in ('-','(')): 
4951 ####                dbg('value:      "%s"' % text) 
4952 ####                dbg('fstr:       "%s"' % fstr) 
4953 ####                dbg("erased:     '%s'" % erased) 
4954                 field_sel_start 
= sel_start 
- start
 
4955                 field_sel_to 
= sel_to 
- start
 
4956 ##                dbg('left fstr:  "%s"' % fstr[1:field_sel_start]) 
4957 ##                dbg('right fstr: "%s"' % fstr[field_sel_to:end]) 
4958                 fstr 
= fstr
[1:field_sel_start
] + char 
+ fstr
[field_sel_to
:end
] 
4959             if field
._alignRight 
and sel_start 
!= sel_to
: 
4960                 field_len 
= end 
- start
 
4961 ##                pos += (field_len - len(fstr))    # move cursor right by deleted amount 
4963 ##                dbg('setting pos to:', pos) 
4965                     fstr 
= '0' * (field_len 
- len(fstr
)) + fstr
 
4967                     fstr 
= fstr
.rjust(field_len
)   # adjust the field accordingly 
4968 ##            dbg('field str: "%s"' % fstr) 
4970             newtext 
= text
[:start
] + fstr 
+ text
[end
:] 
4971             if erased 
in ('-', '(') and self
._signOk
: 
4972                 newtext 
= erased 
+ newtext
[1:] 
4973 ##            dbg('newtext: "%s"' % newtext) 
4975             if self
._signOk 
and field
._index 
== 0: 
4976                 start 
-= 1             # account for sign position 
4978 ####            dbg('field._moveOnFieldFull?', field._moveOnFieldFull) 
4979 ####            dbg('len(fstr.lstrip()) == end-start?', len(fstr.lstrip()) == end-start) 
4980             if( field
._moveOnFieldFull 
and pos 
== end
 
4981                 and len(fstr
.lstrip()) == end
-start
):   # if field now full 
4982                 newpos 
= self
._findNextEntry
(end
)       #   go to next field 
4984                 newpos 
= pos                            
# else keep cursor at current position 
4987 ##            dbg('not newtext') 
4989 ##                dbg('newpos:', newpos) 
4991             if self
._signOk 
and self
._useParens
: 
4992                 old_right_signpos 
= text
.find(')') 
4994             if field
._allowInsert 
and not field
._insertRight 
and sel_to 
<= end 
and sel_start 
>= start
: 
4995 ##                dbg('inserting within a left-insert-capable field') 
4996                 field_len 
= end 
- start
 
4997                 before 
= text
[start
:sel_start
] 
4998                 after 
= text
[sel_to
:end
].strip() 
4999 ####                dbg("current field:'%s'" % text[start:end]) 
5000 ####                dbg("before:'%s'" % before, "after:'%s'" % after) 
5001                 new_len 
= len(before
) + len(after
) + 1 # (for inserted char) 
5002 ####                dbg('new_len:', new_len) 
5004                 if new_len 
< field_len
: 
5005                     retained 
= after 
+ self
._template
[end
-(field_len
-new_len
):end
] 
5006                 elif new_len 
> end
-start
: 
5007                     retained 
= after
[1:] 
5011                 left 
= text
[0:start
] + before
 
5012 ####                dbg("left:'%s'" % left, "retained:'%s'" % retained) 
5013                 right   
= retained 
+ text
[end
:] 
5016                 right   
= text
[pos
+1:] 
5018             if 'unicode' in wx
.PlatformInfo 
and type(char
) != types
.UnicodeType
: 
5019                 # convert the keyboard constant to a unicode value, to 
5020                 # ensure it can be concatenated into the control value: 
5021                 char 
= char
.decode(self
._defaultEncoding
) 
5023             newtext 
= left 
+ char 
+ right
 
5024 ####            dbg('left:    "%s"' % left) 
5025 ####            dbg('right:   "%s"' % right) 
5026 ####            dbg('newtext: "%s"' % newtext) 
5028             if self
._signOk 
and self
._useParens
: 
5029                 # Balance parentheses: 
5030                 left_signpos 
= newtext
.find('(') 
5032                 if left_signpos 
== -1:     # erased '('; remove ')' 
5033                     right_signpos 
= newtext
.find(')') 
5034                     if right_signpos 
!= -1: 
5035                         newtext 
= newtext
[:right_signpos
] + ' ' + newtext
[right_signpos
+1:] 
5037                 elif old_right_signpos 
!= -1: 
5038                     right_signpos 
= newtext
.find(')') 
5040                     if right_signpos 
== -1: # just replaced right-paren 
5041                         if newtext
[pos
] == ' ': # we just erased '); erase '(' 
5042                             newtext 
= newtext
[:left_signpos
] + ' ' + newtext
[left_signpos
+1:] 
5043                         else:   # replaced with digit; move ') over 
5044                             if self
._ctrl
_constraints
._alignRight 
or self
._isFloat
: 
5045                                 newtext 
= newtext
[:-1] + ')' 
5047                                 rstripped_text 
= newtext
.rstrip() 
5048                                 right_signpos 
= len(rstripped_text
) 
5049 ##                                dbg('old_right_signpos:', old_right_signpos, 'right signpos now:', right_signpos) 
5050                                 newtext 
= newtext
[:right_signpos
] + ')' + newtext
[right_signpos
+1:] 
5052             if( field
._insertRight                                  
# if insert-right field (but we didn't start at right edge) 
5053                 and field
._moveOnFieldFull                          
# and should move cursor when full 
5054                 and len(newtext
[start
:end
].strip()) == end
-start
):  # and field now full 
5055                 newpos 
= self
._findNextEntry
(end
)                   #   go to next field 
5056 ##                dbg('newpos = nextentry =', newpos) 
5058 ##                dbg('pos:', pos, 'newpos:', pos+1) 
5063             new_select_to 
= newpos     
# (default return values) 
5067             if field
._autoSelect
: 
5068                 match_index
, partial_match 
= self
._autoComplete
(1,  # (always forward) 
5069                                                                 field
._compareChoices
, 
5071                                                                 compareNoCase
=field
._compareNoCase
, 
5072                                                                 current_index 
= field
._autoCompleteIndex
-1) 
5073                 if match_index 
is not None and partial_match
: 
5074                     matched_str 
= newtext
[start
:end
] 
5075                     newtext 
= newtext
[:start
] + field
._choices
[match_index
] + newtext
[end
:] 
5078                     if field
._insertRight
: 
5079                         # adjust position to just after partial match in field 
5080                         newpos 
= end 
- (len(field
._choices
[match_index
].strip()) - len(matched_str
.strip())) 
5082             elif self
._ctrl
_constraints
._autoSelect
: 
5083                 match_index
, partial_match 
= self
._autoComplete
( 
5084                                         1,  # (always forward) 
5085                                         self
._ctrl
_constraints
._compareChoices
, 
5087                                         self
._ctrl
_constraints
._compareNoCase
, 
5088                                         current_index 
= self
._ctrl
_constraints
._autoCompleteIndex 
- 1) 
5089                 if match_index 
is not None and partial_match
: 
5090                     matched_str 
= newtext
 
5091                     newtext 
= self
._ctrl
_constraints
._choices
[match_index
] 
5092                     edit_end 
= self
._ctrl
_constraints
._extent
[1] 
5093                     new_select_to 
= min(edit_end
, len(newtext
.rstrip())) 
5094                     match_field 
= self
._ctrl
_constraints
 
5095                     if self
._ctrl
_constraints
._insertRight
: 
5096                         # adjust position to just after partial match in control: 
5097                         newpos 
= self
._masklength 
- (len(self
._ctrl
_constraints
._choices
[match_index
].strip()) - len(matched_str
.strip())) 
5099 ##            dbg('newtext: "%s"' % newtext, 'newpos:', newpos, 'new_select_to:', new_select_to) 
5101             return newtext
, newpos
, new_select_to
, match_field
, match_index
 
5103 ##            dbg('newtext: "%s"' % newtext, 'newpos:', newpos) 
5105             return newtext
, newpos
 
5108     def _OnFocus(self
,event
): 
5110         This event handler is currently necessary to work around new default 
5111         behavior as of wxPython2.3.3; 
5112         The TAB key auto selects the entire contents of the wx.TextCtrl *after* 
5113         the EVT_SET_FOCUS event occurs; therefore we can't query/adjust the selection 
5114         *here*, because it hasn't happened yet.  So to prevent this behavior, and 
5115         preserve the correct selection when the focus event is not due to tab, 
5116         we need to pull the following trick: 
5118 ##        dbg('MaskedEditMixin::_OnFocus') 
5119         if self
.IsBeingDeleted() or self
.GetParent().IsBeingDeleted(): 
5121         wx
.CallAfter(self
._fixSelection
) 
5126     def _CheckValid(self
, candidate
=None): 
5128         This is the default validation checking routine; It verifies that the 
5129         current value of the control is a "valid value," and has the side 
5130         effect of coloring the control appropriately. 
5133 ##        dbg('MaskedEditMixin::_CheckValid: candidate="%s"' % candidate, indent=1) 
5134         oldValid 
= self
._valid
 
5135         if candidate 
is None: value 
= self
._GetValue
() 
5136         else: value 
= candidate
 
5137 ##        dbg('value: "%s"' % value) 
5139         valid 
= True    # assume True 
5141         if not self
.IsDefault(value
) and self
._isDate
:                    ## Date type validation 
5142             valid 
= self
._validateDate
(value
) 
5143 ##            dbg("valid date?", valid) 
5145         elif not self
.IsDefault(value
) and self
._isTime
: 
5146             valid 
= self
._validateTime
(value
) 
5147 ##            dbg("valid time?", valid) 
5149         elif not self
.IsDefault(value
) and (self
._isInt 
or self
._isFloat
):  ## Numeric type 
5150             valid 
= self
._validateNumeric
(value
) 
5151 ##            dbg("valid Number?", valid) 
5153         if valid
:   # and not self.IsDefault(value):    ## generic validation accounts for IsDefault() 
5154             ## valid so far; ensure also allowed by any list or regex provided: 
5155             valid 
= self
._validateGeneric
(value
) 
5156 ##            dbg("valid value?", valid) 
5158 ##        dbg('valid?', valid) 
5162             self
._applyFormatting
() 
5163             if self
._valid 
!= oldValid
: 
5164 ##                dbg('validity changed: oldValid =',oldValid,'newvalid =', self._valid) 
5165 ##                dbg('oldvalue: "%s"' % oldvalue, 'newvalue: "%s"' % self._GetValue()) 
5167 ##        dbg(indent=0, suspend=0) 
5171     def _validateGeneric(self
, candidate
=None): 
5172         """ Validate the current value using the provided list or Regex filter (if any). 
5174         if candidate 
is None: 
5175             text 
= self
._GetValue
() 
5179         valid 
= True    # assume True 
5180         for i 
in [-1] + self
._field
_indices
:   # process global constraints first: 
5181             field 
= self
._fields
[i
] 
5182             start
, end 
= field
._extent
 
5183             slice = text
[start
:end
] 
5184             valid 
= field
.IsValid(slice) 
5191     def _validateNumeric(self
, candidate
=None): 
5192         """ Validate that the value is within the specified range (if specified.)""" 
5193         if candidate 
is None: value 
= self
._GetValue
() 
5194         else: value 
= candidate
 
5196             groupchar 
= self
._fields
[0]._groupChar
 
5198                 number 
= float(value
.replace(groupchar
, '').replace(self
._decimalChar
, '.').replace('(', '-').replace(')', '')) 
5200                 number 
= long( value
.replace(groupchar
, '').replace('(', '-').replace(')', '')) 
5202                     if self
._fields
[0]._alignRight
: 
5203                         require_digit_at 
= self
._fields
[0]._extent
[1]-1 
5205                         require_digit_at 
= self
._fields
[0]._extent
[0] 
5206 ##                    dbg('require_digit_at:', require_digit_at) 
5207 ##                    dbg("value[rda]: '%s'" % value[require_digit_at]) 
5208                     if value
[require_digit_at
] not in list(string
.digits
): 
5212 ##            dbg('number:', number) 
5213             if self
._ctrl
_constraints
._hasRange
: 
5214                 valid 
= self
._ctrl
_constraints
._rangeLow 
<= number 
<= self
._ctrl
_constraints
._rangeHigh
 
5217             groupcharpos 
= value
.rfind(groupchar
) 
5218             if groupcharpos 
!= -1:  # group char present 
5219 ##                dbg('groupchar found at', groupcharpos) 
5220                 if self
._isFloat 
and groupcharpos 
> self
._decimalpos
: 
5221                     # 1st one found on right-hand side is past decimal point 
5222 ##                    dbg('groupchar in fraction; illegal') 
5225                     integer 
= value
[:self
._decimalpos
].strip() 
5227                     integer 
= value
.strip() 
5228 ##                dbg("integer:'%s'" % integer) 
5229                 if integer
[0] in ('-', '('): 
5230                     integer 
= integer
[1:] 
5231                 if integer
[-1] == ')': 
5232                     integer 
= integer
[:-1] 
5234                 parts 
= integer
.split(groupchar
) 
5235 ##                dbg('parts:', parts) 
5236                 for i 
in range(len(parts
)): 
5237                     if i 
== 0 and abs(int(parts
[0])) > 999: 
5238 ##                        dbg('group 0 too long; illegal') 
5241                     elif i 
> 0 and (len(parts
[i
]) != 3 or ' ' in parts
[i
]): 
5242 ##                        dbg('group %i (%s) not right size; illegal' % (i, parts[i])) 
5246 ##            dbg('value not a valid number') 
5251     def _validateDate(self
, candidate
=None): 
5252         """ Validate the current date value using the provided Regex filter. 
5253             Generally used for character types.BufferType 
5255 ##        dbg('MaskedEditMixin::_validateDate', indent=1) 
5256         if candidate 
is None: value 
= self
._GetValue
() 
5257         else: value 
= candidate
 
5258 ##        dbg('value = "%s"' % value) 
5259         text 
= self
._adjustDate
(value
, force4digit_year
=True)     ## Fix the date up before validating it 
5260 ##        dbg('text =', text) 
5261         valid 
= True   # assume True until proven otherwise 
5264             # replace fillChar in each field with space: 
5265             datestr 
= text
[0:self
._dateExtent
] 
5267                 field 
= self
._fields
[i
] 
5268                 start
, end 
= field
._extent
 
5269                 fstr 
= datestr
[start
:end
] 
5270                 fstr
.replace(field
._fillChar
, ' ') 
5271                 datestr 
= datestr
[:start
] + fstr 
+ datestr
[end
:] 
5273             year
, month
, day 
= _getDateParts( datestr
, self
._datestyle
) 
5275 ##            dbg('self._dateExtent:', self._dateExtent) 
5276             if self
._dateExtent 
== 11: 
5277                 month 
= charmonths_dict
[month
.lower()] 
5281 ##            dbg('year, month, day:', year, month, day) 
5284 ##            dbg('cannot convert string to integer parts') 
5287 ##            dbg('cannot convert string to integer month') 
5291             # use wxDateTime to unambiguously try to parse the date: 
5292             # ### Note: because wxDateTime is *brain-dead* and expects months 0-11, 
5293             # rather than 1-12, so handle accordingly: 
5299 ##                    dbg("trying to create date from values day=%d, month=%d, year=%d" % (day,month,year)) 
5300                     dateHandler 
= wx
.DateTimeFromDMY(day
,month
,year
) 
5304 ##                    dbg('cannot convert string to valid date') 
5310                 # wxDateTime doesn't take kindly to leading/trailing spaces when parsing, 
5311                 # so we eliminate them here: 
5312                 timeStr     
= text
[self
._dateExtent
+1:].strip()         ## time portion of the string 
5314 ##                    dbg('timeStr: "%s"' % timeStr) 
5316                         checkTime    
= dateHandler
.ParseTime(timeStr
) 
5317                         valid 
= checkTime 
== len(timeStr
) 
5321 ##                        dbg('cannot convert string to valid time') 
5323 ##        if valid: dbg('valid date') 
5328     def _validateTime(self
, candidate
=None): 
5329         """ Validate the current time value using the provided Regex filter. 
5330             Generally used for character types.BufferType 
5332 ##        dbg('MaskedEditMixin::_validateTime', indent=1) 
5333         # wxDateTime doesn't take kindly to leading/trailing spaces when parsing, 
5334         # so we eliminate them here: 
5335         if candidate 
is None: value 
= self
._GetValue
().strip() 
5336         else: value 
= candidate
.strip() 
5337 ##        dbg('value = "%s"' % value) 
5338         valid 
= True   # assume True until proven otherwise 
5340         dateHandler 
= wx
.DateTime_Today() 
5342             checkTime    
= dateHandler
.ParseTime(value
) 
5343 ##            dbg('checkTime:', checkTime, 'len(value)', len(value)) 
5344             valid 
= checkTime 
== len(value
) 
5349 ##            dbg('cannot convert string to valid time') 
5351 ##        if valid: dbg('valid time') 
5356     def _OnKillFocus(self
,event
): 
5357         """ Handler for EVT_KILL_FOCUS event. 
5359 ##        dbg('MaskedEditMixin::_OnKillFocus', 'isDate=',self._isDate, indent=1) 
5360         if self
.IsBeingDeleted() or self
.GetParent().IsBeingDeleted(): 
5362         if self
._mask 
and self
._IsEditable
(): 
5363             self
._AdjustField
(self
._GetInsertionPoint
()) 
5364             self
._CheckValid
()   ## Call valid handler 
5366         self
._LostFocus
()    ## Provided for subclass use 
5371     def _fixSelection(self
): 
5373         This gets called after the TAB traversal selection is made, if the 
5374         focus event was due to this, but before the EVT_LEFT_* events if 
5375         the focus shift was due to a mouse event. 
5377         The trouble is that, a priori, there's no explicit notification of 
5378         why the focus event we received.  However, the whole reason we need to 
5379         do this is because the default behavior on TAB traveral in a wx.TextCtrl is 
5380         now to select the entire contents of the window, something we don't want. 
5381         So we can *now* test the selection range, and if it's "the whole text" 
5382         we can assume the cause, change the insertion point to the start of 
5383         the control, and deselect. 
5385 ##        dbg('MaskedEditMixin::_fixSelection', indent=1) 
5386         # can get here if called with wx.CallAfter after underlying  
5387         # control has been destroyed on close, but after focus 
5389         if not self 
or not self
._mask 
or not self
._IsEditable
(): 
5393         sel_start
, sel_to 
= self
._GetSelection
() 
5394 ##        dbg('sel_start, sel_to:', sel_start, sel_to, 'self.IsEmpty()?', self.IsEmpty()) 
5396         if( sel_start 
== 0 and sel_to 
>= len( self
._mask 
)   #(can be greater in numeric controls because of reserved space) 
5397             and (not self
._ctrl
_constraints
._autoSelect 
or self
.IsEmpty() or self
.IsDefault() ) ): 
5398             # This isn't normally allowed, and so assume we got here by the new 
5399             # "tab traversal" behavior, so we need to reset the selection 
5400             # and insertion point: 
5401 ##            dbg('entire text selected; resetting selection to start of control') 
5403             field 
= self
._FindField
(self
._GetInsertionPoint
()) 
5404             edit_start
, edit_end 
= field
._extent
 
5405             if field
._selectOnFieldEntry
: 
5406                 if self
._isFloat 
or self
._isInt 
and field 
== self
._fields
[0]: 
5408                 self
._SetInsertionPoint
(edit_start
) 
5409                 self
._SetSelection
(edit_start
, edit_end
) 
5411             elif field
._insertRight
: 
5412                 self
._SetInsertionPoint
(edit_end
) 
5413                 self
._SetSelection
(edit_end
, edit_end
) 
5415         elif (self
._isFloat 
or self
._isInt
): 
5417             text
, signpos
, right_signpos 
= self
._getAbsValue
() 
5418             if text 
is None or text 
== self
._template
: 
5419                 integer 
= self
._fields
[0] 
5420                 edit_start
, edit_end 
= integer
._extent
 
5422                 if integer
._selectOnFieldEntry
: 
5423 ##                    dbg('select on field entry:') 
5424                     self
._SetInsertionPoint
(0) 
5425                     self
._SetSelection
(0, edit_end
) 
5427                 elif integer
._insertRight
: 
5428 ##                    dbg('moving insertion point to end') 
5429                     self
._SetInsertionPoint
(edit_end
) 
5430                     self
._SetSelection
(edit_end
, edit_end
) 
5432 ##                    dbg('numeric ctrl is empty; start at beginning after sign') 
5433                     self
._SetInsertionPoint
(signpos
+1)   ## Move past minus sign space if signed 
5434                     self
._SetSelection
(signpos
+1, signpos
+1) 
5436         elif sel_start 
> self
._goEnd
(getPosOnly
=True): 
5437 ##            dbg('cursor beyond the end of the user input; go to end of it') 
5440 ##            dbg('sel_start, sel_to:', sel_start, sel_to, 'self._masklength:', self._masklength) 
5445     def _Keypress(self
,key
): 
5446         """ Method provided to override OnChar routine. Return False to force 
5447             a skip of the 'normal' OnChar process. Called before class OnChar. 
5452     def _LostFocus(self
): 
5453         """ Method provided for subclasses. _LostFocus() is called after 
5454             the class processes its EVT_KILL_FOCUS event code. 
5459     def _OnDoubleClick(self
, event
): 
5460         """ selects field under cursor on dclick.""" 
5461         pos 
= self
._GetInsertionPoint
() 
5462         field 
= self
._FindField
(pos
) 
5463         start
, end 
= field
._extent
 
5464         self
._SetInsertionPoint
(start
) 
5465         self
._SetSelection
(start
, end
) 
5469         """ Method provided for subclasses. Called by internal EVT_TEXT 
5470             handler. Return False to override the class handler, True otherwise. 
5477         Used to override the default Cut() method in base controls, instead 
5478         copying the selection to the clipboard and then blanking the selection, 
5479         leaving only the mask in the selected area behind. 
5480         Note: _Cut (read "undercut" ;-) must be called from a Cut() override in the 
5481         derived control because the mixin functions can't override a method of 
5484 ##        dbg("MaskedEditMixin::_Cut", indent=1) 
5485         value 
= self
._GetValue
() 
5486 ##        dbg('current value: "%s"' % value) 
5487         sel_start
, sel_to 
= self
._GetSelection
()                   ## check for a range of selected text 
5488 ##        dbg('selected text: "%s"' % value[sel_start:sel_to].strip()) 
5489         do 
= wx
.TextDataObject() 
5490         do
.SetText(value
[sel_start
:sel_to
].strip()) 
5491         wx
.TheClipboard
.Open() 
5492         wx
.TheClipboard
.SetData(do
) 
5493         wx
.TheClipboard
.Close() 
5495         if sel_to 
- sel_start 
!= 0: 
5500 # WS Note: overriding Copy is no longer necessary given that you 
5501 # can no longer select beyond the last non-empty char in the control. 
5503 ##    def _Copy( self ): 
5505 ##        Override the wx.TextCtrl's .Copy function, with our own 
5506 ##        that does validation.  Need to strip trailing spaces. 
5508 ##        sel_start, sel_to = self._GetSelection() 
5509 ##        select_len = sel_to - sel_start 
5510 ##        textval = wx.TextCtrl._GetValue(self) 
5512 ##        do = wx.TextDataObject() 
5513 ##        do.SetText(textval[sel_start:sel_to].strip()) 
5514 ##        wx.TheClipboard.Open() 
5515 ##        wx.TheClipboard.SetData(do) 
5516 ##        wx.TheClipboard.Close() 
5519     def _getClipboardContents( self 
): 
5520         """ Subroutine for getting the current contents of the clipboard. 
5522         do 
= wx
.TextDataObject() 
5523         wx
.TheClipboard
.Open() 
5524         success 
= wx
.TheClipboard
.GetData(do
) 
5525         wx
.TheClipboard
.Close() 
5530             # Remove leading and trailing spaces before evaluating contents 
5531             return do
.GetText().strip() 
5534     def _validatePaste(self
, paste_text
, sel_start
, sel_to
, raise_on_invalid
=False): 
5536         Used by paste routine and field choice validation to see 
5537         if a given slice of paste text is legal for the area in question: 
5538         returns validity, replacement text, and extent of paste in 
5542 ##        dbg('MaskedEditMixin::_validatePaste("%(paste_text)s", %(sel_start)d, %(sel_to)d), raise_on_invalid? %(raise_on_invalid)d' % locals(), indent=1) 
5543         select_length 
= sel_to 
- sel_start
 
5544         maxlength 
= select_length
 
5545 ##        dbg('sel_to - sel_start:', maxlength) 
5547             maxlength 
= self
._masklength 
- sel_start
 
5551 ##        dbg('maxlength:', maxlength) 
5552         if 'unicode' in wx
.PlatformInfo 
and type(paste_text
) != types
.UnicodeType
: 
5553             paste_text 
= paste_text
.decode(self
._defaultEncoding
) 
5555         length_considered 
= len(paste_text
) 
5556         if length_considered 
> maxlength
: 
5557 ##            dbg('paste text will not fit into the %s:' % item, indent=0) 
5558             if raise_on_invalid
: 
5559 ##                dbg(indent=0, suspend=0) 
5560                 if item 
== 'control': 
5561                     raise ValueError('"%s" will not fit into the control "%s"' % (paste_text
, self
.name
)) 
5563                     raise ValueError('"%s" will not fit into the selection' % paste_text
) 
5565 ##                dbg(indent=0, suspend=0) 
5566                 return False, None, None 
5568         text 
= self
._template
 
5569 ##        dbg('length_considered:', length_considered) 
5572         replacement_text 
= "" 
5573         replace_to 
= sel_start
 
5575         while valid_paste 
and i 
< length_considered 
and replace_to 
< self
._masklength
: 
5576             if paste_text
[i
:] == self
._template
[replace_to
:length_considered
]: 
5577                 # remainder of paste matches template; skip char-by-char analysis 
5578 ##                dbg('remainder paste_text[%d:] (%s) matches template[%d:%d]' % (i, paste_text[i:], replace_to, length_considered)) 
5579                 replacement_text 
+= paste_text
[i
:] 
5580                 replace_to 
= i 
= length_considered
 
5583             char 
= paste_text
[i
] 
5584             field 
= self
._FindField
(replace_to
) 
5585             if not field
._compareNoCase
: 
5586                 if field
._forceupper
:   char 
= char
.upper() 
5587                 elif field
._forcelower
: char 
= char
.lower() 
5589 ##            dbg('char:', "'"+char+"'", 'i =', i, 'replace_to =', replace_to) 
5590 ##            dbg('self._isTemplateChar(%d)?' % replace_to, self._isTemplateChar(replace_to)) 
5591             if not self
._isTemplateChar
(replace_to
) and self
._isCharAllowed
( char
, replace_to
, allowAutoSelect
=False, ignoreInsertRight
=True): 
5592                 replacement_text 
+= char
 
5593 ##                dbg("not template(%(replace_to)d) and charAllowed('%(char)s',%(replace_to)d)" % locals()) 
5594 ##                dbg("replacement_text:", '"'+replacement_text+'"') 
5597             elif( char 
== self
._template
[replace_to
] 
5598                   or (self
._signOk 
and 
5599                           ( (i 
== 0 and (char 
== '-' or (self
._useParens 
and char 
== '('))) 
5600                             or (i 
== self
._masklength 
- 1 and self
._useParens 
and char 
== ')') ) ) ): 
5601                 replacement_text 
+= char
 
5602 ##                dbg("'%(char)s' == template(%(replace_to)d)" % locals()) 
5603 ##                dbg("replacement_text:", '"'+replacement_text+'"') 
5607                 next_entry 
= self
._findNextEntry
(replace_to
, adjustInsert
=False) 
5608                 if next_entry 
== replace_to
: 
5611                     replacement_text 
+= self
._template
[replace_to
:next_entry
] 
5612 ##                    dbg("skipping template; next_entry =", next_entry) 
5613 ##                    dbg("replacement_text:", '"'+replacement_text+'"') 
5614                     replace_to 
= next_entry  
# so next_entry will be considered on next loop 
5616         if not valid_paste 
and raise_on_invalid
: 
5617 ##            dbg('raising exception', indent=0, suspend=0) 
5618             raise ValueError('"%s" cannot be inserted into the control "%s"' % (paste_text
, self
.name
)) 
5620         elif i 
< len(paste_text
): 
5622             if raise_on_invalid
: 
5623 ##                dbg('raising exception', indent=0, suspend=0) 
5624                 raise ValueError('"%s" will not fit into the control "%s"' % (paste_text
, self
.name
)) 
5626 ##        dbg('valid_paste?', valid_paste) 
5628 ##            dbg('replacement_text: "%s"' % replacement_text, 'replace to:', replace_to) 
5630 ##        dbg(indent=0, suspend=0) 
5631         return valid_paste
, replacement_text
, replace_to
 
5634     def _Paste( self
, value
=None, raise_on_invalid
=False, just_return_value
=False ): 
5636         Used to override the base control's .Paste() function, 
5637         with our own that does validation. 
5638         Note: _Paste must be called from a Paste() override in the 
5639         derived control because the mixin functions can't override a 
5640         method of a sibling class. 
5642 ##        dbg('MaskedEditMixin::_Paste (value = "%s")' % value, indent=1) 
5644             paste_text 
= self
._getClipboardContents
() 
5648         if paste_text 
is not None: 
5650             if 'unicode' in wx
.PlatformInfo 
and type(paste_text
) != types
.UnicodeType
: 
5651                 paste_text 
= paste_text
.decode(self
._defaultEncoding
) 
5653 ##            dbg('paste text: "%s"' % paste_text) 
5654             # (conversion will raise ValueError if paste isn't legal) 
5655             sel_start
, sel_to 
= self
._GetSelection
() 
5656 ##            dbg('selection:', (sel_start, sel_to)) 
5658             # special case: handle allowInsert fields properly 
5659             field 
= self
._FindField
(sel_start
) 
5660             edit_start
, edit_end 
= field
._extent
 
5662             if field
._allowInsert 
and sel_to 
<= edit_end 
and (sel_start 
+ len(paste_text
) < edit_end 
or field
._insertRight
): 
5663                 if field
._insertRight
: 
5664                     # want to paste to the left; see if it will fit: 
5665                     left_text 
= self
._GetValue
()[edit_start
:sel_start
].lstrip() 
5666 ##                    dbg('len(left_text):', len(left_text)) 
5667 ##                    dbg('len(paste_text):', len(paste_text)) 
5668 ##                    dbg('sel_start - (len(left_text) + len(paste_text)) >= edit_start?', sel_start - (len(left_text) + len(paste_text)) >= edit_start) 
5669                     if sel_start 
- (len(left_text
) - (sel_to 
- sel_start
) + len(paste_text
)) >= edit_start
: 
5670                         # will fit! create effective paste text, and move cursor back to do so: 
5671                         paste_text 
= left_text 
+ paste_text
 
5672                         sel_start 
-= len(left_text
) 
5673                         paste_text 
= paste_text
.rjust(sel_to 
- sel_start
) 
5674 ##                        dbg('modified paste_text to be: "%s"' % paste_text) 
5675 ##                        dbg('modified selection to:', (sel_start, sel_to)) 
5677 ##                        dbg("won't fit left;", 'paste text remains: "%s"' % paste_text) 
5680                     paste_text 
= paste_text 
+ self
._GetValue
()[sel_to
:edit_end
].rstrip() 
5681 ##                    dbg("allow insert, but not insert right;", 'paste text set to: "%s"' % paste_text) 
5684                 new_pos 
= sel_start 
+ len(paste_text
)   # store for subsequent positioning 
5685 ##                dbg('paste within insertable field; adjusted paste_text: "%s"' % paste_text, 'end:', edit_end) 
5686 ##                dbg('expanded selection to:', (sel_start, sel_to)) 
5688             # Another special case: paste won't fit, but it's a right-insert field where entire 
5689             # non-empty value is selected, and there's room if the selection is expanded leftward: 
5690             if( len(paste_text
) > sel_to 
- sel_start
 
5691                 and field
._insertRight
 
5692                 and sel_start 
> edit_start
 
5693                 and sel_to 
>= edit_end
 
5694                 and not self
._GetValue
()[edit_start
:sel_start
].strip() ): 
5695                 # text won't fit within selection, but left of selection is empty; 
5696                 # check to see if we can expand selection to accommodate the value: 
5697                 empty_space 
= sel_start 
- edit_start
 
5698                 amount_needed 
= len(paste_text
) - (sel_to 
- sel_start
) 
5699                 if amount_needed 
<= empty_space
: 
5700                     sel_start 
-= amount_needed
 
5701 ##                    dbg('expanded selection to:', (sel_start, sel_to)) 
5704             # another special case: deal with signed values properly: 
5706                 signedvalue
, signpos
, right_signpos 
= self
._getSignedValue
() 
5707                 paste_signpos 
= paste_text
.find('-') 
5708                 if paste_signpos 
== -1: 
5709                     paste_signpos 
= paste_text
.find('(') 
5711                 # if paste text will result in signed value: 
5712 ####                dbg('paste_signpos != -1?', paste_signpos != -1) 
5713 ####                dbg('sel_start:', sel_start, 'signpos:', signpos) 
5714 ####                dbg('field._insertRight?', field._insertRight) 
5715 ####                dbg('sel_start - len(paste_text) >= signpos?', sel_start - len(paste_text) <= signpos) 
5716                 if paste_signpos 
!= -1 and (sel_start 
<= signpos
 
5717                                             or (field
._insertRight 
and sel_start 
- len(paste_text
) <= signpos
)): 
5721                 # remove "sign" from paste text, so we can auto-adjust for sign type after paste: 
5722                 paste_text 
= paste_text
.replace('-', ' ').replace('(',' ').replace(')','') 
5723 ##                dbg('unsigned paste text: "%s"' % paste_text) 
5727             # another special case: deal with insert-right fields when selection is empty and 
5728             # cursor is at end of field: 
5729 ####            dbg('field._insertRight?', field._insertRight) 
5730 ####            dbg('sel_start == edit_end?', sel_start == edit_end) 
5731 ####            dbg('sel_start', sel_start, 'sel_to', sel_to) 
5732             if field
._insertRight 
and sel_start 
== edit_end 
and sel_start 
== sel_to
: 
5733                 sel_start 
-= len(paste_text
) 
5736 ##                dbg('adjusted selection:', (sel_start, sel_to)) 
5739                 valid_paste
, replacement_text
, replace_to 
= self
._validatePaste
(paste_text
, sel_start
, sel_to
, raise_on_invalid
) 
5741 ##                dbg('exception thrown', indent=0) 
5745 ##                dbg('paste text not legal for the selection or portion of the control following the cursor;') 
5746                 if not wx
.Validator_IsSilent(): 
5751             text 
= self
._eraseSelection
() 
5753             new_text 
= text
[:sel_start
] + replacement_text 
+ text
[replace_to
:] 
5755                 new_text 
= string
.ljust(new_text
,self
._masklength
) 
5757                 new_text
, signpos
, right_signpos 
= self
._getSignedValue
(candidate
=new_text
) 
5760                         new_text 
= new_text
[:signpos
] + '(' + new_text
[signpos
+1:right_signpos
] + ')' + new_text
[right_signpos
+1:] 
5762                         new_text 
= new_text
[:signpos
] + '-' + new_text
[signpos
+1:] 
5766 ##            dbg("new_text:", '"'+new_text+'"') 
5768             if not just_return_value
: 
5769                 if new_text 
!= self
._GetValue
(): 
5770                     self
.modified 
= True 
5774                     wx
.CallAfter(self
._SetValue
, new_text
) 
5776                         new_pos 
= sel_start 
+ len(replacement_text
) 
5777                     wx
.CallAfter(self
._SetInsertionPoint
, new_pos
) 
5780                 return new_text
, replace_to
 
5781         elif just_return_value
: 
5783             return self
._GetValue
(), sel_to
 
5786     def _Undo(self
, value
=None, prev
=None, just_return_results
=False): 
5787         """ Provides an Undo() method in base controls. """ 
5788 ##        dbg("MaskedEditMixin::_Undo", indent=1) 
5790             value 
= self
._GetValue
() 
5792             prev 
= self
._prevValue
 
5793 ##        dbg('current value:  "%s"' % value) 
5794 ##        dbg('previous value: "%s"' % prev) 
5796 ##            dbg('no previous value', indent=0) 
5800             # Determine what to select: (relies on fixed-length strings) 
5801             # (This is a lot harder than it would first appear, because 
5802             # of mask chars that stay fixed, and so break up the "diff"...) 
5804             # Determine where they start to differ: 
5806             length 
= len(value
)     # (both are same length in masked control) 
5808             while( value
[:i
] == prev
[:i
] ): 
5813             # handle signed values carefully, so undo from signed to unsigned or vice-versa 
5816                 text
, signpos
, right_signpos 
= self
._getSignedValue
(candidate
=prev
) 
5818                     if prev
[signpos
] == '(' and prev
[right_signpos
] == ')': 
5822                     # eliminate source of "far-end" undo difference if using balanced parens: 
5823                     value 
= value
.replace(')', ' ') 
5824                     prev 
= prev
.replace(')', ' ') 
5825                 elif prev
[signpos
] == '-': 
5830             # Determine where they stop differing in "undo" result: 
5831             sm 
= difflib
.SequenceMatcher(None, a
=value
, b
=prev
) 
5832             i
, j
, k 
= sm
.find_longest_match(sel_start
, length
, sel_start
, length
) 
5833 ##            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] ) 
5835             if k 
== 0:                              # no match found; select to end 
5838                 code_5tuples 
= sm
.get_opcodes() 
5839                 for op
, i1
, i2
, j1
, j2 
in code_5tuples
: 
5840 ##                    dbg("%7s value[%d:%d] (%s) prev[%d:%d] (%s)" % (op, i1, i2, value[i1:i2], j1, j2, prev[j1:j2])) 
5844                 # look backward through operations needed to produce "previous" value; 
5845                 # first change wins: 
5846                 for next_op 
in range(len(code_5tuples
)-1, -1, -1): 
5847                     op
, i1
, i2
, j1
, j2 
= code_5tuples
[next_op
] 
5848 ##                    dbg('value[i1:i2]: "%s"' % value[i1:i2], 'template[i1:i2] "%s"' % self._template[i1:i2]) 
5849                     field 
= self
._FindField
(i2
) 
5850                     if op 
== 'insert' and prev
[j1
:j2
] != self
._template
[j1
:j2
]: 
5851 ##                        dbg('insert found: selection =>', (j1, j2)) 
5856                     elif op 
== 'delete' and value
[i1
:i2
] != self
._template
[i1
:i2
]: 
5857                         edit_start
, edit_end 
= field
._extent
 
5858                         if field
._insertRight 
and (field
._allowInsert 
or i2 
== edit_end
): 
5864 ##                        dbg('delete found: selection =>', (sel_start, sel_to)) 
5867                     elif op 
== 'replace': 
5868                         if not prev
[i1
:i2
].strip() and field
._insertRight
: 
5869                             sel_start 
= sel_to 
= j2
 
5873 ##                        dbg('replace found: selection =>', (sel_start, sel_to)) 
5879                     # now go forwards, looking for earlier changes: 
5880 ##                    dbg('searching forward...') 
5881                     for next_op 
in range(len(code_5tuples
)): 
5882                         op
, i1
, i2
, j1
, j2 
= code_5tuples
[next_op
] 
5883                         field 
= self
._FindField
(i1
) 
5886                         elif op 
== 'replace': 
5887                             if field
._insertRight
: 
5888                                 # if replace with spaces in an insert-right control, ignore "forward" replace 
5889                                 if not prev
[i1
:i2
].strip(): 
5892 ##                                    dbg('setting sel_start to', j1) 
5895 ##                                    dbg('setting sel_start to', i1) 
5898 ##                                dbg('setting sel_start to', i1) 
5900 ##                            dbg('saw replace; breaking') 
5902                         elif op 
== 'insert' and not value
[i1
:i2
]: 
5903 ##                            dbg('forward %s found' % op) 
5904                             if prev
[j1
:j2
].strip(): 
5905 ##                                dbg('item to insert non-empty; setting sel_start to', j1) 
5908                             elif not field
._insertRight
: 
5909 ##                                dbg('setting sel_start to inserted space:', j1) 
5912                         elif op 
== 'delete': 
5913 ##                            dbg('delete; field._insertRight?', field._insertRight, 'value[%d:%d].lstrip: "%s"' % (i1,i2,value[i1:i2].lstrip())) 
5914                             if field
._insertRight
: 
5915                                 if value
[i1
:i2
].lstrip(): 
5916 ##                                    dbg('setting sel_start to ', j1) 
5918 ##                                    dbg('breaking loop') 
5923 ##                                dbg('saw delete; breaking') 
5926 ##                            dbg('unknown code!') 
5927                             # we've got what we need 
5932 ##                    dbg('no insert,delete or replace found (!)') 
5933                     # do "left-insert"-centric processing of difference based on l.c.s.: 
5934                     if i 
== j 
and j 
!= sel_start
:         # match starts after start of selection 
5935                         sel_to 
= sel_start 
+ (j
-sel_start
)  # select to start of match 
5937                         sel_to 
= j                          
# (change ends at j) 
5940             # There are several situations where the calculated difference is 
5941             # not what we want to select.  If changing sign, or just adding 
5942             # group characters, we really don't want to highlight the characters 
5943             # changed, but instead leave the cursor where it is. 
5944             # Also, there a situations in which the difference can be ambiguous; 
5947             # current value:    11234 
5948             # previous value:   1111234 
5950             # Where did the cursor actually lie and which 1s were selected on the delete 
5953             # Also, difflib can "get it wrong;" Consider: 
5955             # current value:    "       128.66" 
5956             # previous value:   "       121.86" 
5958             # difflib produces the following opcodes, which are sub-optimal: 
5959             #    equal value[0:9] (       12) prev[0:9] (       12) 
5960             #   insert value[9:9] () prev[9:11] (1.) 
5961             #    equal value[9:10] (8) prev[11:12] (8) 
5962             #   delete value[10:11] (.) prev[12:12] () 
5963             #    equal value[11:12] (6) prev[12:13] (6) 
5964             #   delete value[12:13] (6) prev[13:13] () 
5966             # This should have been: 
5967             #    equal value[0:9] (       12) prev[0:9] (       12) 
5968             #  replace value[9:11] (8.6) prev[9:11] (1.8) 
5969             #    equal value[12:13] (6) prev[12:13] (6) 
5971             # But it didn't figure this out! 
5973             # To get all this right, we use the previous selection recorded to help us... 
5975             if (sel_start
, sel_to
) != self
._prevSelection
: 
5976 ##                dbg('calculated selection', (sel_start, sel_to), "doesn't match previous", self._prevSelection) 
5978                 prev_sel_start
, prev_sel_to 
= self
._prevSelection
 
5979                 field 
= self
._FindField
(sel_start
) 
5981                       and sel_start 
< self
._masklength
 
5982                       and (prev
[sel_start
] in ('-', '(', ')') 
5983                                      or value
[sel_start
] in ('-', '(', ')')) ): 
5984                     # change of sign; leave cursor alone... 
5985 ##                    dbg("prev[sel_start] in ('-', '(', ')')?", prev[sel_start] in ('-', '(', ')')) 
5986 ##                    dbg("value[sel_start] in ('-', '(', ')')?", value[sel_start] in ('-', '(', ')')) 
5987 ##                    dbg('setting selection to previous one') 
5988                     sel_start
, sel_to 
= self
._prevSelection
 
5990                 elif field
._groupdigits 
and (value
[sel_start
:sel_to
] == field
._groupChar
 
5991                                              or prev
[sel_start
:sel_to
] == field
._groupChar
): 
5992                     # do not highlight grouping changes 
5993 ##                    dbg('value[sel_start:sel_to] == field._groupChar?', value[sel_start:sel_to] == field._groupChar) 
5994 ##                    dbg('prev[sel_start:sel_to] == field._groupChar?', prev[sel_start:sel_to] == field._groupChar) 
5995 ##                    dbg('setting selection to previous one') 
5996                     sel_start
, sel_to 
= self
._prevSelection
 
5999                     calc_select_len 
= sel_to 
- sel_start
 
6000                     prev_select_len 
= prev_sel_to 
- prev_sel_start
 
6002 ##                    dbg('sel_start == prev_sel_start', sel_start == prev_sel_start) 
6003 ##                    dbg('sel_to > prev_sel_to', sel_to > prev_sel_to) 
6005                     if prev_select_len 
>= calc_select_len
: 
6006                         # old selection was bigger; trust it: 
6007 ##                        dbg('prev_select_len >= calc_select_len?', prev_select_len >= calc_select_len) 
6008                         if not field
._insertRight
: 
6009 ##                            dbg('setting selection to previous one') 
6010                             sel_start
, sel_to 
= self
._prevSelection
 
6012                             sel_to 
= self
._prevSelection
[1] 
6013 ##                            dbg('setting selection to', (sel_start, sel_to)) 
6015                     elif( sel_to 
> prev_sel_to                  
# calculated select past last selection 
6016                           and prev_sel_to 
< len(self
._template
) # and prev_sel_to not at end of control 
6017                           and sel_to 
== len(self
._template
) ):  # and calculated selection goes to end of control 
6019                         i
, j
, k 
= sm
.find_longest_match(prev_sel_to
, length
, prev_sel_to
, length
) 
6020 ##                        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] ) 
6022                             # difflib must not have optimized opcodes properly; 
6026                         # look for possible ambiguous diff: 
6028                         # if last change resulted in no selection, test from resulting cursor position: 
6029                         if prev_sel_start 
== prev_sel_to
: 
6030                             calc_select_len 
= sel_to 
- sel_start
 
6031                             field 
= self
._FindField
(prev_sel_start
) 
6033                             # determine which way to search from last cursor position for ambiguous change: 
6034                             if field
._insertRight
: 
6035                                 test_sel_start 
= prev_sel_start
 
6036                                 test_sel_to 
= prev_sel_start 
+ calc_select_len
 
6038                                 test_sel_start 
= prev_sel_start 
- calc_select_len
 
6039                                 test_sel_to 
= prev_sel_start
 
6041                             test_sel_start
, test_sel_to 
= prev_sel_start
, prev_sel_to
 
6043 ##                        dbg('test selection:', (test_sel_start, test_sel_to)) 
6044 ##                        dbg('calc change: "%s"' % prev[sel_start:sel_to]) 
6045 ##                        dbg('test change: "%s"' % prev[test_sel_start:test_sel_to]) 
6047                         # if calculated selection spans characters, and same characters 
6048                         # "before" the previous insertion point are present there as well, 
6049                         # select the ones related to the last known selection instead. 
6050                         if( sel_start 
!= sel_to
 
6051                             and test_sel_to 
< len(self
._template
) 
6052                             and prev
[test_sel_start
:test_sel_to
] == prev
[sel_start
:sel_to
] ): 
6054                             sel_start
, sel_to 
= test_sel_start
, test_sel_to
 
6056                 # finally, make sure that the old and new values are 
6057                 # different where we say they're different: 
6058                 while( sel_to 
- 1 > 0 
6059                         and sel_to 
> sel_start
 
6060                         and value
[sel_to
-1:] == prev
[sel_to
-1:]): 
6062                 while( sel_start 
+ 1 < self
._masklength
 
6063                         and sel_start 
< sel_to
 
6064                         and value
[:sel_start
+1] == prev
[:sel_start
+1]): 
6067 ##            dbg('sel_start, sel_to:', sel_start, sel_to) 
6068 ##            dbg('previous value: "%s"' % prev) 
6070             if just_return_results
: 
6071                 return prev
, (sel_start
, sel_to
) 
6073             self
._SetValue
(prev
) 
6074             self
._SetInsertionPoint
(sel_start
) 
6075             self
._SetSelection
(sel_start
, sel_to
) 
6078 ##            dbg('no difference between previous value') 
6080             if just_return_results
: 
6081                 return prev
, self
._GetSelection
() 
6084     def _OnClear(self
, event
): 
6085         """ Provides an action for context menu delete operation """ 
6089     def _OnContextMenu(self
, event
): 
6090 ##        dbg('MaskedEditMixin::OnContextMenu()', indent=1) 
6092         menu
.Append(wx
.ID_UNDO
, "Undo", "") 
6093         menu
.AppendSeparator() 
6094         menu
.Append(wx
.ID_CUT
, "Cut", "") 
6095         menu
.Append(wx
.ID_COPY
, "Copy", "") 
6096         menu
.Append(wx
.ID_PASTE
, "Paste", "") 
6097         menu
.Append(wx
.ID_CLEAR
, "Delete", "") 
6098         menu
.AppendSeparator() 
6099         menu
.Append(wx
.ID_SELECTALL
, "Select All", "") 
6101         wx
.EVT_MENU(menu
, wx
.ID_UNDO
, self
._OnCtrl
_Z
) 
6102         wx
.EVT_MENU(menu
, wx
.ID_CUT
, self
._OnCtrl
_X
) 
6103         wx
.EVT_MENU(menu
, wx
.ID_COPY
, self
._OnCtrl
_C
) 
6104         wx
.EVT_MENU(menu
, wx
.ID_PASTE
, self
._OnCtrl
_V
) 
6105         wx
.EVT_MENU(menu
, wx
.ID_CLEAR
, self
._OnClear
) 
6106         wx
.EVT_MENU(menu
, wx
.ID_SELECTALL
, self
._OnCtrl
_A
) 
6108         # ## WSS: The base control apparently handles 
6109         # enable/disable of wx.ID_CUT, wx.ID_COPY, wx.ID_PASTE 
6110         # and wx.ID_CLEAR menu items even if the menu is one 
6111         # we created.  However, it doesn't do undo properly, 
6112         # so we're keeping track of previous values ourselves. 
6113         # Therefore, we have to override the default update for 
6114         # that item on the menu: 
6115         wx
.EVT_UPDATE_UI(self
, wx
.ID_UNDO
, self
._UndoUpdateUI
) 
6116         self
._contextMenu 
= menu
 
6118         self
.PopupMenu(menu
, event
.GetPosition()) 
6120         self
._contextMenu 
= None 
6123     def _UndoUpdateUI(self
, event
): 
6124         if self
._prevValue 
is None or self
._prevValue 
== self
._curValue
: 
6125             self
._contextMenu
.Enable(wx
.ID_UNDO
, False) 
6127             self
._contextMenu
.Enable(wx
.ID_UNDO
, True) 
6130     def _OnCtrlParametersChanged(self
): 
6132         Overridable function to allow derived classes to take action as a 
6133         result of parameter changes prior to possibly changing the value 
6138  ## ---------- ---------- ---------- ---------- ---------- ---------- ---------- 
6139 class MaskedEditAccessorsMixin
: 
6141     To avoid a ton of boiler-plate, and to automate the getter/setter generation 
6142     for each valid control parameter so we never forget to add the functions when 
6143     adding parameters, this class programmatically adds the masked edit mixin 
6144     parameters to itself. 
6145     (This makes it easier for Designers like Boa to deal with masked controls.) 
6147     To further complicate matters, this is done with an extra level of inheritance, 
6148     so that "general" classes like masked.TextCtrl can have all possible attributes, 
6149     while derived classes, like masked.TimeCtrl and masked.NumCtrl can prevent 
6150     exposure of those optional attributes of their base class that do not make 
6151     sense for their derivation. 
6153     Therefore, we define: 
6154         BaseMaskedTextCtrl(TextCtrl, MaskedEditMixin) 
6156         masked.TextCtrl(BaseMaskedTextCtrl, MaskedEditAccessorsMixin). 
6158     This allows us to then derive: 
6159         masked.NumCtrl( BaseMaskedTextCtrl ) 
6161     and not have to expose all the same accessor functions for the 
6162     derived control when they don't all make sense for it. 
6166     # Define the default set of attributes exposed by the most generic masked controls: 
6167     exposed_basectrl_params 
= MaskedEditMixin
.valid_ctrl_params
.keys() + Field
.valid_params
.keys() 
6168     exposed_basectrl_params
.remove('index') 
6169     exposed_basectrl_params
.remove('extent') 
6170     exposed_basectrl_params
.remove('foregroundColour')   # (base class already has this) 
6172     for param 
in exposed_basectrl_params
: 
6173         propname 
= param
[0].upper() + param
[1:] 
6174         exec('def Set%s(self, value): self.SetCtrlParameters(%s=value)' % (propname
, param
)) 
6175         exec('def Get%s(self): return self.GetCtrlParameter("%s")''' % (propname, param)) 
6177         if param.find('Colour
') != -1: 
6178             # add non-british spellings, for backward-compatibility 
6179             propname.replace('Colour
', 'Color
') 
6181             exec('def Set
%s(self
, value
): self
.SetCtrlParameters(%s=value
)' % (propname, param)) 
6182             exec('def Get
%s(self
): return self
.GetCtrlParameter("%s")''' % (propname, param)) 
6187 ## ---------- ---------- ---------- ---------- ---------- ---------- ---------- 
6188 ## these are helper subroutines: 
6190 def _movetofloat( origvalue, fmtstring, neg, addseparators=False, sepchar = ',',fillchar=' '): 
6191     """ addseparators = add separator character every three numerals if True 
6193     fmt0 = fmtstring.split('.') 
6196     val  = origvalue.split('.')[0].strip() 
6197     ret  = fillchar * (len(fmt1)-len(val)) + val + "." + "0" * len(fmt2) 
6200     return (ret,len(fmt1)) 
6203 def _isDateType( fmtstring ): 
6204     """ Checks the mask and returns True if it fits an allowed 
6205         date or datetime format. 
6207     dateMasks = ("^##/##/####", 
6219     reString  = "|".join(dateMasks) 
6220     filter = re.compile( reString) 
6221     if re.match(filter,fmtstring): return True 
6224 def _isTimeType( fmtstring ): 
6225     """ Checks the mask and returns True if it fits an allowed 
6228     reTimeMask = "^##:##(:##)?( (AM|PM))?" 
6229     filter = re.compile( reTimeMask ) 
6230     if re.match(filter,fmtstring): return True 
6234 def _isFloatingPoint( fmtstring): 
6235     filter = re.compile("[ ]?[#]+\.[#]+\n") 
6236     if re.match(filter,fmtstring+"\n"): return True 
6240 def _isInteger( fmtstring ): 
6241     filter = re.compile("[#]+\n") 
6242     if re.match(filter,fmtstring+"\n"): return True 
6246 def _getDateParts( dateStr, dateFmt ): 
6247     if len(dateStr) > 11: clip = dateStr[0:11] 
6248     else:                 clip = dateStr 
6249     if clip[-2] not in string.digits: 
6250         clip = clip[:-1]    # (got part of time; drop it) 
6252     dateSep = (('/' in clip) * '/') + (('-' in clip) * '-') + (('.' in clip) * '.') 
6253     slices  = clip.split(dateSep) 
6254     if dateFmt == "MDY": 
6255         y,m,d = (slices[2],slices[0],slices[1])  ## year, month, date parts 
6256     elif dateFmt == "DMY": 
6257         y,m,d = (slices[2],slices[1],slices[0])  ## year, month, date parts 
6258     elif dateFmt == "YMD": 
6259         y,m,d = (slices[0],slices[1],slices[2])  ## year, month, date parts 
6261         y,m,d = None, None, None 
6268 def _getDateSepChar(dateStr): 
6269     clip   = dateStr[0:10] 
6270     dateSep = (('/' in clip) * '/') + (('-' in clip) * '-') + (('.' in clip) * '.') 
6274 def _makeDate( year, month, day, dateFmt, dateStr): 
6275     sep    = _getDateSepChar( dateStr) 
6276     if dateFmt == "MDY": 
6277         return "%s%s%s%s%s" % (month,sep,day,sep,year)  ## year, month, date parts 
6278     elif dateFmt == "DMY": 
6279         return "%s%s%s%s%s" % (day,sep,month,sep,year)  ## year, month, date parts 
6280     elif dateFmt == "YMD": 
6281         return "%s%s%s%s%s" % (year,sep,month,sep,day)  ## year, month, date parts 
6286 def _getYear(dateStr,dateFmt): 
6287     parts = _getDateParts( dateStr, dateFmt) 
6290 def _getMonth(dateStr,dateFmt): 
6291     parts = _getDateParts( dateStr, dateFmt) 
6294 def _getDay(dateStr,dateFmt): 
6295     parts = _getDateParts( dateStr, dateFmt) 
6298 ## ---------- ---------- ---------- ---------- ---------- ---------- ---------- 
6299 class __test(wx.PySimpleApp): 
6301             from wx.lib.rcsizer import RowColSizer 
6302             self.frame = wx.Frame( None, -1, "MaskedEditMixin 0.0.7 Demo Page #1", size = (700,600)) 
6303             self.panel = wx.Panel( self.frame, -1) 
6304             self.sizer = RowColSizer() 
6309             id, id1 = wx.NewId(), wx.NewId() 
6310             self.command1  = wx.Button( self.panel, id, "&Close" ) 
6311             self.command2  = wx.Button( self.panel, id1, "&AutoFormats" ) 
6312             self.sizer.Add(self.command1, row=0, col=0, flag=wx.ALL, border = 5) 
6313             self.sizer.Add(self.command2, row=0, col=1, colspan=2, flag=wx.ALL, border = 5) 
6314             self.panel.Bind(wx.EVT_BUTTON, self.onClick, self.command1 ) 
6315 ##            self.panel.SetDefaultItem(self.command1 ) 
6316             self.panel.Bind(wx.EVT_BUTTON, self.onClickPage, self.command2) 
6318             self.check1 = wx.CheckBox( self.panel, -1, "Disallow Empty" ) 
6319             self.check2 = wx.CheckBox( self.panel, -1, "Highlight Empty" ) 
6320             self.sizer.Add( self.check1, row=0,col=3, flag=wx.ALL,border=5 ) 
6321             self.sizer.Add( self.check2, row=0,col=4, flag=wx.ALL,border=5 ) 
6322             self.panel.Bind(wx.EVT_CHECKBOX, self._onCheck1, self.check1 ) 
6323             self.panel.Bind(wx.EVT_CHECKBOX, self._onCheck2, self.check2 ) 
6326             label = """Press ctrl-s in any field to output the value and plain value. Press ctrl-x to clear and re-set any field. 
6327 Note that all controls have been auto-sized by including F in the format code. 
6328 Try entering nonsensical or partial values in validated fields to see what happens (use ctrl-s to test the valid status).""" 
6329             label2 = "\nNote that the State and Last Name fields are list-limited (Name:Smith,Jones,Williams)." 
6331             self.label1 = wx.StaticText( self.panel, -1, label) 
6332             self.label2 = wx.StaticText( self.panel, -1, "Description") 
6333             self.label3 = wx.StaticText( self.panel, -1, "Mask Value") 
6334             self.label4 = wx.StaticText( self.panel, -1, "Format") 
6335             self.label5 = wx.StaticText( self.panel, -1, "Reg Expr Val. (opt)") 
6336             self.label6 = wx.StaticText( self.panel, -1, "MaskedEdit Ctrl") 
6337             self.label7 = wx.StaticText( self.panel, -1, label2) 
6338             self.label7.SetForegroundColour("Blue") 
6339             self.label1.SetForegroundColour("Blue") 
6340             self.label2.SetFont(wx.Font(9,wx.SWISS,wx.NORMAL,wx.BOLD)) 
6341             self.label3.SetFont(wx.Font(9,wx.SWISS,wx.NORMAL,wx.BOLD)) 
6342             self.label4.SetFont(wx.Font(9,wx.SWISS,wx.NORMAL,wx.BOLD)) 
6343             self.label5.SetFont(wx.Font(9,wx.SWISS,wx.NORMAL,wx.BOLD)) 
6344             self.label6.SetFont(wx.Font(9,wx.SWISS,wx.NORMAL,wx.BOLD)) 
6346             self.sizer.Add( self.label1, row=1,col=0,colspan=7, flag=wx.ALL,border=5) 
6347             self.sizer.Add( self.label7, row=2,col=0,colspan=7, flag=wx.ALL,border=5) 
6348             self.sizer.Add( self.label2, row=3,col=0, flag=wx.ALL,border=5) 
6349             self.sizer.Add( self.label3, row=3,col=1, flag=wx.ALL,border=5) 
6350             self.sizer.Add( self.label4, row=3,col=2, flag=wx.ALL,border=5) 
6351             self.sizer.Add( self.label5, row=3,col=3, flag=wx.ALL,border=5) 
6352             self.sizer.Add( self.label6, row=3,col=4, flag=wx.ALL,border=5) 
6354             # The following list is of the controls for the demo. Feel free to play around with 
6357             #description        mask                    excl format     regexp                              range,list,initial 
6358            ("Phone No",         "(###) ###-#### x:###", "", 'F!^-R',    "^\(\d\d\d\) \d\d\d-\d\d\d\d",    (),[],''), 
6359            ("Last Name Only",   "C{14}",                "", 'F {list}', '^[A-Z][a-zA-Z]+',                  (),('Smith','Jones','Williams'),''), 
6360            ("Full Name",        "C{14}",                "", 'F_',       '^[A-Z][a-zA-Z]+ [A-Z][a-zA-Z]+',   (),[],''), 
6361            ("Social Sec#",      "###-##-####",          "", 'F',        "\d{3}-\d{2}-\d{4}",                (),[],''), 
6362            ("U.S. Zip+4",       "#{5}-#{4}",            "", 'F',        "\d{5}-(\s{4}|\d{4})",(),[],''), 
6363            ("U.S. State (2 char)\n(with default)","AA",                 "", 'F!',       "[A-Z]{2}",                         (),states, 'AZ'), 
6364            ("Customer No",      "\CAA-###",              "", 'F!',      "C[A-Z]{2}-\d{3}",                   (),[],''), 
6365            ("Date (MDY) + Time\n(with default)",      "##/##/#### ##:## AM",  'BCDEFGHIJKLMNOQRSTUVWXYZ','DFR!',"",                (),[], r'03/05/2003 12:00 AM'), 
6366            ("Invoice Total",    "#{9}.##",              "", 'F-R,',     "",                                 (),[], ''), 
6367            ("Integer (signed)\n(with default)", "#{6}",                 "", 'F-R',      "",                                 (),[], '0     '), 
6368            ("Integer (unsigned)\n(with default), 1-399", "######",      "", 'F',        "",                                 (1,399),[], '1     '), 
6369            ("Month selector",   "XXX",                  "", 'F',        "",                                 (), 
6370                 ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'],""), 
6371            ("fraction selector","#/##",                 "", 'F',        "^\d\/\d\d?",                       (), 
6372                 ['2/3', '3/4', '1/2', '1/4', '1/8', '1/16', '1/32', '1/64'], "") 
6375             for control in controls: 
6376                 self.sizer.Add( wx.StaticText( self.panel, -1, control[0]),row=rowcount, col=0,border=5,flag=wx.ALL) 
6377                 self.sizer.Add( wx.StaticText( self.panel, -1, control[1]),row=rowcount, col=1,border=5, flag=wx.ALL) 
6378                 self.sizer.Add( wx.StaticText( self.panel, -1, control[3]),row=rowcount, col=2,border=5, flag=wx.ALL) 
6379                 self.sizer.Add( wx.StaticText( self.panel, -1, control[4][:20]),row=rowcount, col=3,border=5, flag=wx.ALL) 
6381                 if control in controls[:]:#-2]: 
6382                     newControl  = MaskedTextCtrl( self.panel, -1, "", 
6384                                                     excludeChars = control[2], 
6385                                                     formatcodes  = control[3], 
6387                                                     validRegex   = control[4], 
6388                                                     validRange   = control[5], 
6389                                                     choices      = control[6], 
6390                                                     defaultValue = control[7], 
6392                     if control[6]: newControl.SetCtrlParameters(choiceRequired = True) 
6394                     newControl = MaskedComboBox(  self.panel, -1, "", 
6395                                                     choices = control[7], 
6396                                                     choiceRequired  = True, 
6398                                                     formatcodes  = control[3], 
6399                                                     excludeChars = control[2], 
6401                                                     validRegex   = control[4], 
6402                                                     validRange   = control[5], 
6404                 self.editList.append( newControl ) 
6406                 self.sizer.Add( newControl, row=rowcount,col=4,flag=wx.ALL,border=5) 
6409             self.sizer.AddGrowableCol(4) 
6411             self.panel.SetSizer(self.sizer) 
6412             self.panel.SetAutoLayout(1) 
6419         def onClick(self, event): 
6422         def onClickPage(self, event): 
6423             self.page2 = __test2(self.frame,-1,"") 
6424             self.page2.Show(True) 
6426         def _onCheck1(self,event): 
6427             """ Set required value on/off """ 
6428             value = event.IsChecked() 
6430                 for control in self.editList: 
6431                     control.SetCtrlParameters(emptyInvalid=True) 
6434                 for control in self.editList: 
6435                     control.SetCtrlParameters(emptyInvalid=False) 
6437             self.panel.Refresh() 
6439         def _onCheck2(self,event): 
6440             """ Highlight empty values""" 
6441             value = event.IsChecked() 
6443                 for control in self.editList: 
6444                     control.SetCtrlParameters( emptyBackgroundColour = 'Aquamarine') 
6447                 for control in self.editList: 
6448                     control.SetCtrlParameters( emptyBackgroundColour = 'White') 
6450             self.panel.Refresh() 
6453 ## ---------- ---------- ---------- ---------- ---------- ---------- ---------- 
6455 class __test2(wx.Frame): 
6456         def __init__(self, parent, id, caption): 
6457             wx.Frame.__init__( self, parent, id, "MaskedEdit control 0.0.7 Demo Page #2 -- AutoFormats", size = (550,600)) 
6458             from wx.lib.rcsizer import RowColSizer 
6459             self.panel = wx.Panel( self, -1) 
6460             self.sizer = RowColSizer() 
6466 All these controls have been created by passing a single parameter, the AutoFormat code. 
6467 The class contains an internal dictionary of types and formats (autoformats). 
6468 To see a great example of validations in action, try entering a bad email address, then tab out.""" 
6470             self.label1 = wx.StaticText( self.panel, -1, label) 
6471             self.label2 = wx.StaticText( self.panel, -1, "Description") 
6472             self.label3 = wx.StaticText( self.panel, -1, "AutoFormat Code") 
6473             self.label4 = wx.StaticText( self.panel, -1, "MaskedEdit Control") 
6474             self.label1.SetForegroundColour("Blue") 
6475             self.label2.SetFont(wx.Font(9,wx.SWISS,wx.NORMAL,wx.BOLD)) 
6476             self.label3.SetFont(wx.Font(9,wx.SWISS,wx.NORMAL,wx.BOLD)) 
6477             self.label4.SetFont(wx.Font(9,wx.SWISS,wx.NORMAL,wx.BOLD)) 
6479             self.sizer.Add( self.label1, row=1,col=0,colspan=3, flag=wx.ALL,border=5) 
6480             self.sizer.Add( self.label2, row=3,col=0, flag=wx.ALL,border=5) 
6481             self.sizer.Add( self.label3, row=3,col=1, flag=wx.ALL,border=5) 
6482             self.sizer.Add( self.label4, row=3,col=2, flag=wx.ALL,border=5) 
6484             id, id1 = wx.NewId(), wx.NewId() 
6485             self.command1  = wx.Button( self.panel, id, "&Close") 
6486             self.command2  = wx.Button( self.panel, id1, "&Print Formats") 
6487             self.panel.Bind(wx.EVT_BUTTON, self.onClick, self.command1) 
6488             self.panel.SetDefaultItem(self.command1) 
6489             self.panel.Bind(wx.EVT_BUTTON, self.onClickPrint, self.command2) 
6491             # The following list is of the controls for the demo. Feel free to play around with 
6494            ("Phone No","USPHONEFULLEXT"), 
6495            ("US Date + Time","USDATETIMEMMDDYYYY/HHMM"), 
6496            ("US Date MMDDYYYY","USDATEMMDDYYYY/"), 
6497            ("Time (with seconds)","TIMEHHMMSS"), 
6498            ("Military Time\n(without seconds)","24HRTIMEHHMM"), 
6499            ("Social Sec#","USSOCIALSEC"), 
6500            ("Credit Card","CREDITCARD"), 
6501            ("Expiration MM/YY","EXPDATEMMYY"), 
6502            ("Percentage","PERCENT"), 
6503            ("Person's Age","AGE"), 
6504            ("US Zip Code","USZIP"), 
6505            ("US Zip+4","USZIPPLUS4"), 
6506            ("Email Address","EMAIL"), 
6507            ("IP Address", "(derived control IpAddrCtrl)") 
6510             for control in controls: 
6511                 self.sizer.Add( wx.StaticText( self.panel, -1, control[0]),row=rowcount, col=0,border=5,flag=wx.ALL) 
6512                 self.sizer.Add( wx.StaticText( self.panel, -1, control[1]),row=rowcount, col=1,border=5, flag=wx.ALL) 
6513                 if control in controls[:-1]: 
6514                     self.sizer.Add( MaskedTextCtrl( self.panel, -1, "", 
6515                                                       autoformat  = control[1], 
6517                                 row=rowcount,col=2,flag=wx.ALL,border=5) 
6519                     self.sizer.Add( IpAddrCtrl( self.panel, -1, "", demo=True ), 
6520                                     row=rowcount,col=2,flag=wx.ALL,border=5) 
6523             self.sizer.Add(self.command1, row=0, col=0, flag=wx.ALL, border = 5) 
6524             self.sizer.Add(self.command2, row=0, col=1, flag=wx.ALL, border = 5) 
6525             self.sizer.AddGrowableCol(3) 
6527             self.panel.SetSizer(self.sizer) 
6528             self.panel.SetAutoLayout(1) 
6530         def onClick(self, event): 
6533         def onClickPrint(self, event): 
6534             for format in masktags.keys(): 
6535                 sep = "+------------------------+" 
6536                 print "%s\n%s  \n  Mask: %s \n  RE Validation string: %s\n" % (sep,format, masktags[format]['mask'], masktags[format]['validRegex']) 
6538 ## ---------- ---------- ---------- ---------- ---------- ---------- ---------- 
6540 if __name__ == "__main__": 
6546 ## =================================== 
6548 ## 1. WS: For some reason I don't understand, the control is generating two (2) 
6549 ##      EVT_TEXT events for every one (1) .SetValue() of the underlying control. 
6550 ##      I've been unsuccessful in determining why or in my efforts to make just one 
6551 ##      occur.  So, I've added a hack to save the last seen value from the 
6552 ##      control in the EVT_TEXT handler, and if *different*, call event.Skip() 
6553 ##      to propagate it down the event chain, and let the application see it. 
6555 ## 2. WS: MaskedComboBox is deficient in several areas, all having to do with the 
6556 ##      behavior of the underlying control that I can't fix.  The problems are: 
6557 ##      a) The background coloring doesn't work in the text field of the control; 
6558 ##         instead, there's a only border around it that assumes the correct color. 
6559 ##      b) The control will not pass WXK_TAB to the event handler, no matter what 
6560 ##         I do, and there's no style wxCB_PROCESS_TAB like wxTE_PROCESS_TAB to 
6561 ##         indicate that we want these events.  As a result, MaskedComboBox 
6562 ##         doesn't do the nice field-tabbing that MaskedTextCtrl does. 
6563 ##      c) Auto-complete had to be reimplemented for the control because programmatic 
6564 ##         setting of the value of the text field does not set up the auto complete 
6565 ##         the way that the control processing keystrokes does.  (But I think I've 
6566 ##         implemented a fairly decent approximation.)  Because of this the control 
6567 ##         also won't auto-complete on dropdown, and there's no event I can catch 
6568 ##         to work around this problem. 
6569 ##      d) There is no method provided for getting the selection; the hack I've 
6570 ##         implemented has its flaws, not the least of which is that due to the 
6571 ##         strategy that I'm using, the paste buffer is always replaced by the 
6572 ##         contents of the control's selection when in focus, on each keystroke; 
6573 ##         this makes it impossible to paste anything into a MaskedComboBox 
6574 ##         at the moment... :-( 
6575 ##      e) The other deficient behavior, likely induced by the workaround for (d), 
6576 ##         is that you can can't shift-left to select more than one character 
6580 ## 3. WS: Controls on wxPanels don't seem to pass Shift-WXK_TAB to their 
6581 ##      EVT_KEY_DOWN or EVT_CHAR event handlers.  Until this is fixed in 
6582 ##      wxWindows, shift-tab won't take you backwards through the fields of 
6583 ##      a MaskedTextCtrl like it should.  Until then Shifted arrow keys will 
6584 ##      work like shift-tab and tab ought to. 
6588 ## =============================## 
6589 ##  1. Add Popup list for auto-completable fields that simulates combobox on individual 
6590 ##     fields.  Example: City validates against list of cities, or zip vs zip code list. 
6591 ##  2. Allow optional monetary symbols (eg. $, pounds, etc.) at front of a "decimal" 
6593 ##  3. Fix shift-left selection for MaskedComboBox. 
6594 ##  5. Transform notion of "decimal control" to be less "entire control"-centric, 
6595 ##     so that monetary symbols can be included and still have the appropriate 
6596 ##     semantics.  (Big job, as currently written, but would make control even 
6597 ##     more useful for business applications.) 
6601 ## ==================== 
6603 ##  1. Added handling for WXK_DELETE and WXK_INSERT, such that shift-delete 
6604 ##     cuts, shift-insert pastes, and ctrl-insert copies. 
6607 ##  1. Now ignores kill focus events when being destroyed. 
6608 ##  2. Added missing call to set insertion point on changing fields. 
6609 ##  3. Modified SetKeyHandler() to accept None as means of removing one. 
6610 ##  4. Fixed keyhandler processing for group and decimal character changes. 
6611 ##  5. Fixed a problem that prevented input into the integer digit of a  
6612 ##     integerwidth=1 numctrl, if the current value was 0. 
6613 ##  6. Fixed logic involving processing of "_signOk" flag, to remove default 
6614 ##     sign key handlers if false, so that SetAllowNegative(False) in the 
6615 ##     NumCtrl works properly. 
6616 ##  7. Fixed selection logic for numeric controls so that if selectOnFieldEntry 
6617 ##     is true, and the integer portion of an integer format control is selected 
6618 ##     and the sign position is selected, the sign keys will always result in a 
6619 ##     negative value, rather than toggling the previous sign. 
6623 ##  1. Fixed bug involving incorrect variable name, causing combobox autocomplete to fail. 
6624 ##  2. Added proper support for unicode version of wxPython 
6625 ##  3. Added * as mask char meaning "all ansi chars" (ordinals 32-255). 
6626 ##  4. Converted doc strings to use reST format, for ePyDoc documentation. 
6627 ##  5. Renamed helper functions, classes, etc. not intended to be visible in public 
6628 ##     interface to code. 
6631 ##  1. Fixed intra-right-insert-field erase, such that it doesn't leave a hole, but instead 
6632 ##     shifts the text to the left accordingly. 
6633 ##  2. Fixed _SetValue() to place cursor after last character inserted, rather than end of 
6635 ##  3. Fixed some incorrect undo behavior for right-insert fields, and allowed derived classes 
6636 ##     (eg. numctrl) to pass modified values for undo processing (to handle/ignore grouping 
6638 ##  4. Fixed autoselect behavior to work similarly to (2) above, so that combobox 
6639 ##     selection will only select the non-empty text, as per request. 
6640 ##  5. Fixed tabbing to work with 2.5.2 semantics. 
6641 ##  6. Fixed size calculation to handle changing fonts 
6644 ##  1. Reorganized masked controls into separate package, renamed things accordingly 
6645 ##  2. Split actual controls out of this file into their own files. 
6647 ##  (Reported) bugs fixed: 
6648 ##   1. Crash ensues if you attempt to change the mask of a read-only 
6649 ##      MaskedComboBox after initial construction. 
6650 ##   2. Changed strategy of defining Get/Set property functions so that 
6651 ##      these are now generated dynamically at runtime, rather than as 
6652 ##      part of the class definition.  (This makes it possible to have 
6653 ##      more general base classes that have many more options for configuration 
6654 ##      without requiring that derivations support the same options.) 
6655 ##   3. Fixed IsModified for _Paste() and _OnErase(). 
6658 ##   1. Fixed "attribute function inheritance," since base control is more 
6659 ##      generic than subsequent derivations, not all property functions of a 
6660 ##      generic control should be exposed in those derivations.  New strategy 
6661 ##      uses base control classes (eg. BaseMaskedTextCtrl) that should be 
6662 ##      used to derive new class types, and mixed with their own mixins to 
6663 ##      only expose those attributes from the generic masked controls that 
6664 ##      make sense for the derivation.  (This makes Boa happier.) 
6665 ##   2. Renamed (with b-c) MILTIME autoformats to 24HRTIME, so as to be less 
6669 ##  (Reported) bugs fixed: 
6670 ##   1. Right-click menu allowed "cut" operation that destroyed mask 
6671 ##      (was implemented by base control) 
6672 ##   2. MaskedComboBox didn't allow .Append() of mixed-case values; all 
6673 ##      got converted to lower case. 
6674 ##   3. MaskedComboBox selection didn't deal with spaces in values 
6675 ##      properly when autocompleting, and didn't have a concept of "next" 
6676 ##      match for handling choice list duplicates. 
6677 ##   4. Size of MaskedComboBox was always default. 
6678 ##   5. Email address regexp allowed some "non-standard" things, and wasn't 
6680 ##   6. Couldn't easily reset MaskedComboBox contents programmatically. 
6681 ##   7. Couldn't set emptyInvalid during construction. 
6682 ##   8. Under some versions of wxPython, readonly comboboxes can apparently 
6683 ##      return a GetInsertionPoint() result (655535), causing masked control 
6685 ##   9. Specifying an empty mask caused the controls to traceback. 
6686 ##  10. Can't specify float ranges for validRange. 
6687 ##  11. '.' from within a the static portion of a restricted IP address 
6688 ##      destroyed the mask from that point rightward; tab when cursor is 
6689 ##      before 1st field takes cursor past that field. 
6692 ##  12. Added Ctrl-Z/Undo handling, (and implemented context-menu properly.) 
6693 ##  13. Added auto-select option on char input for masked controls with 
6695 ##  14. Added '>' formatcode, allowing insert within a given or each field 
6696 ##      as appropriate, rather than requiring "overwrite".  This makes single 
6697 ##      field controls that just have validation rules (eg. EMAIL) much more 
6698 ##      friendly.  The same flag controls left shift when deleting vs just 
6699 ##      blanking the value, and for right-insert fields, allows right-insert 
6700 ##      at any non-blank (non-sign) position in the field. 
6701 ##  15. Added option to use to indicate negative values for numeric controls. 
6702 ##  16. Improved OnFocus handling of numeric controls. 
6703 ##  17. Enhanced Home/End processing to allow operation on a field level, 
6705 ##  18. Added individual Get/Set functions for control parameters, for 
6706 ##      simplified integration with Boa Constructor. 
6707 ##  19. Standardized "Colour" parameter names to match wxPython, with 
6708 ##      non-british spellings still supported for backward-compatibility. 
6709 ##  20. Added '&' mask specification character for punctuation only (no letters 
6711 ##  21. Added (in a separate file) wx.MaskedCtrl() factory function to provide 
6712 ##      unified interface to the masked edit subclasses. 
6716 ##   1. Made it possible to configure grouping, decimal and shift-decimal characters, 
6717 ##      to make controls more usable internationally. 
6718 ##   2. Added code to smart "adjust" value strings presented to .SetValue() 
6719 ##      for right-aligned numeric format controls if they are shorter than 
6720 ##      than the control width,  prepending the missing portion, prepending control 
6721 ##      template left substring for the missing characters, so that setting 
6722 ##      numeric values is easier. 
6723 ##   3. Renamed SetMaskParameters SetCtrlParameters() (with old name preserved 
6724 ##      for b-c), as this makes more sense. 
6727 ##   1. Fixed .SetValue() to replace the current value, rather than the current 
6728 ##      selection. Also changed it to generate ValueError if presented with 
6729 ##      either a value which doesn't follow the format or won't fit.  Also made 
6730 ##      set value adjust numeric and date controls as if user entered the value. 
6731 ##      Expanded doc explaining how SetValue() works. 
6732 ##   2. Fixed EUDATE* autoformats, fixed IsDateType mask list, and added ability to 
6733 ##      use 3-char months for dates, and EUDATETIME, and EUDATEMILTIME autoformats. 
6734 ##   3. Made all date autoformats automatically pick implied "datestyle". 
6735 ##   4. Added IsModified override, since base wx.TextCtrl never reports modified if 
6736 ##      .SetValue used to change the value, which is what the masked edit controls 
6738 ##   5. Fixed bug in date position adjustment on 2 to 4 digit date conversion when 
6739 ##      using tab to "leave field" and auto-adjust. 
6740 ##   6. Fixed bug in _isCharAllowed() for negative number insertion on pastes, 
6741 ##      and bug in ._Paste() that didn't account for signs in signed masks either. 
6742 ##   7. Fixed issues with _adjustPos for right-insert fields causing improper 
6743 ##      selection/replacement of values 
6744 ##   8. Fixed _OnHome handler to properly handle extending current selection to 
6745 ##      beginning of control. 
6746 ##   9. Exposed all (valid) autoformats to demo, binding descriptions to 
6748 ##  10. Fixed a couple of bugs in email regexp. 
6749 ##  11. Made maskchardict an instance var, to make mask chars to be more 
6750 ##      amenable to international use. 
6751 ##  12. Clarified meaning of '-' formatcode in doc. 
6752 ##  13. Fixed a couple of coding bugs being flagged by Python2.1. 
6753 ##  14. Fixed several issues with sign positioning, erasure and validity 
6754 ##      checking for "numeric" masked controls. 
6755 ##  15. Added validation to IpAddrCtrl.SetValue(). 
6758 ##   1. Changed calling interface to use boolean "useFixedWidthFont" (True by default) 
6759 ##      vs. literal font facename, and use wxTELETYPE as the font family 
6761 ##   2. Switched to use of dbg module vs. locally defined version. 
6762 ##   3. Revamped entire control structure to use Field classes to hold constraint 
6763 ##      and formatting data, to make code more hierarchical, allow for more 
6764 ##      sophisticated masked edit construction. 
6765 ##   4. Better strategy for managing options, and better validation on keywords. 
6766 ##   5. Added 'V' format code, which requires that in order for a character 
6767 ##      to be accepted, it must result in a string that passes the validRegex. 
6768 ##   6. Added 'S' format code which means "select entire field when navigating 
6770 ##   7. Added 'r' format code to allow "right-insert" fields. (implies 'R'--right-alignment) 
6771 ##   8. Added '<' format code to allow fields to require explicit cursor movement 
6773 ##   9. Added validFunc option to other validation mechanisms, that allows derived 
6774 ##      classes to add dynamic validation constraints to the control. 
6775 ##  10. Fixed bug in validatePaste code causing possible IndexErrors, and also 
6776 ##      fixed failure to obey case conversion codes when pasting. 
6777 ##  11. Implemented '0' (zero-pad) formatting code, as it wasn't being done anywhere... 
6778 ##  12. Removed condition from OnDecimalPoint, so that it always truncates right on '.' 
6779 ##  13. Enhanced IpAddrCtrl to use right-insert fields, selection on field traversal, 
6780 ##      individual field validation to prevent field values > 255, and require explicit 
6781 ##      tab/. to change fields. 
6782 ##  14. Added handler for left double-click to select field under cursor. 
6783 ##  15. Fixed handling for "Read-only" styles. 
6784 ##  16. Separated signedForegroundColor from 'R' style, and added foregroundColor 
6785 ##      attribute, for more consistent and controllable coloring. 
6786 ##  17. Added retainFieldValidation parameter, allowing top-level constraints 
6787 ##      such as "validRequired" to be set independently of field-level equivalent. 
6788 ##      (needed in TimeCtrl for bounds constraints.) 
6789 ##  18. Refactored code a bit, cleaned up and commented code more heavily, fixed 
6790 ##      some of the logic for setting/resetting parameters, eg. fillChar, defaultValue, 
6792 ##  19. Fixed maskchar setting for upper/lowercase, to work in all locales. 
6796 ##   1. Decimal point behavior restored for decimal and integer type controls: 
6797 ##      decimal point now trucates the portion > 0. 
6798 ##   2. Return key now works like the tab character and moves to the next field, 
6799 ##      provided no default button is set for the form panel on which the control 
6801 ##   3. Support added in _FindField() for subclasses controls (like timecontrol) 
6802 ##      to determine where the current insertion point is within the mask (i.e. 
6803 ##      which sub-'field'). See method documentation for more info and examples. 
6804 ##   4. Added Field class and support for all constraints to be field-specific 
6805 ##      in addition to being globally settable for the control. 
6806 ##      Choices for each field are validated for length and pastability into 
6807 ##      the field in question, raising ValueError if not appropriate for the control. 
6808 ##      Also added selective additional validation based on individual field constraints. 
6809 ##      By default, SHIFT-WXK_DOWN, SHIFT-WXK_UP, WXK_PRIOR and WXK_NEXT all 
6810 ##      auto-complete fields with choice lists, supplying the 1st entry in 
6811 ##      the choice list if the field is empty, and cycling through the list in 
6812 ##      the appropriate direction if already a match.  WXK_DOWN will also auto- 
6813 ##      complete if the field is partially completed and a match can be made. 
6814 ##      SHIFT-WXK_UP/DOWN will also take you to the next field after any 
6815 ##      auto-completion performed. 
6816 ##   5. Added autoCompleteKeycodes=[] parameters for allowing further 
6817 ##      customization of the control.  Any keycode supplied as a member 
6818 ##      of the _autoCompleteKeycodes list will be treated like WXK_NEXT.  If 
6819 ##      requireFieldChoice is set, then a valid value from each non-empty 
6820 ##      choice list will be required for the value of the control to validate. 
6821 ##   6. Fixed "auto-sizing" to be relative to the font actually used, rather 
6822 ##      than making assumptions about character width. 
6823 ##   7. Fixed GetMaskParameter(), which was non-functional in previous version. 
6824 ##   8. Fixed exceptions raised to provide info on which control had the error. 
6825 ##   9. Fixed bug in choice management of MaskedComboBox. 
6826 ##  10. Fixed bug in IpAddrCtrl causing traceback if field value was of 
6827 ##     the form '# #'.  Modified control code for IpAddrCtrl so that '.' 
6828 ##     in the middle of a field clips the rest of that field, similar to 
6829 ##     decimal and integer controls. 
6833 ##   1. "-" is a toggle for sign; "+" now changes - signed numerics to positive. 
6834 ##   2. ',' in formatcodes now causes numeric values to be comma-delimited (e.g.333,333). 
6835 ##   3. New support for selecting text within the control.(thanks Will Sadkin!) 
6836 ##      Shift-End and Shift-Home now select text as you would expect 
6837 ##      Control-Shift-End selects to the end of the mask string, even if value not entered. 
6838 ##      Control-A selects all *entered* text, Shift-Control-A selects everything in the control. 
6839 ##   4. event.Skip() added to onKillFocus to correct remnants when running in Linux (contributed- 
6840 ##      for some reason I couldn't find the original email but thanks!!!) 
6841 ##   5. All major key-handling code moved to their own methods for easier subclassing: OnHome, 
6842 ##      OnErase, OnEnd, OnCtrl_X, OnCtrl_A, etc. 
6843 ##   6. Email and autoformat validations corrected using regex provided by Will Sadkin (thanks!). 
6844 ##   (The rest of the changes in this version were done by Will Sadkin with permission from Jeff...) 
6845 ##   7. New mechanism for replacing default behavior for any given key, using 
6846 ##      ._SetKeycodeHandler(keycode, func) and ._SetKeyHandler(char, func) now available 
6847 ##      for easier subclassing of the control. 
6848 ##   8. Reworked the delete logic, cut, paste and select/replace logic, as well as some bugs 
6849 ##      with insertion point/selection modification.  Changed Ctrl-X to use standard "cut" 
6850 ##      semantics, erasing the selection, rather than erasing the entire control. 
6851 ##   9. Added option for an "default value" (ie. the template) for use when a single fillChar 
6852 ##      is not desired in every position.  Added IsDefault() function to mean "does the value 
6853 ##      equal the template?" and modified .IsEmpty() to mean "do all of the editable 
6854 ##      positions in the template == the fillChar?" 
6855 ##  10. Extracted mask logic into mixin, so we can have both MaskedTextCtrl and MaskedComboBox, 
6857 ##  11. MaskedComboBox now adds the capability to validate from list of valid values. 
6858 ##      Example: City validates against list of cities, or zip vs zip code list. 
6859 ##  12. Fixed oversight in EVT_TEXT handler that prevented the events from being 
6860 ##      passed to the next handler in the event chain, causing updates to the 
6861 ##      control to be invisible to the parent code. 
6862 ##  13. Added IPADDR autoformat code, and subclass IpAddrCtrl for controlling tabbing within 
6863 ##      the control, that auto-reformats as you move between cells. 
6864 ##  14. Mask characters [A,a,X,#] can now appear in the format string as literals, by using '\'. 
6865 ##  15. It is now possible to specify repeating masks, e.g. #{3}-#{3}-#{14} 
6866 ##  16. Fixed major bugs in date validation, due to the fact that 
6867 ##      wxDateTime.ParseDate is too liberal, and will accept any form that 
6868 ##      makes any kind of sense, regardless of the datestyle you specified 
6869 ##      for the control.  Unfortunately, the strategy used to fix it only 
6870 ##      works for versions of wxPython post 2.3.3.1, as a C++ assert box 
6871 ##      seems to show up on an invalid date otherwise, instead of a catchable 
6873 ##  17. Enhanced date adjustment to automatically adjust heuristic based on 
6874 ##      current year, making last century/this century determination on 
6875 ##      2-digit year based on distance between today's year and value; 
6876 ##      if > 50 year separation, assume last century (and don't assume last 
6877 ##      century is 20th.) 
6878 ##  18. Added autoformats and support for including HHMMSS as well as HHMM for 
6879 ##      date times, and added similar time, and militaray time autoformats. 
6880 ##  19. Enhanced tabbing logic so that tab takes you to the next field if the 
6881 ##      control is a multi-field control. 
6882 ##  20. Added stub method called whenever the control "changes fields", that 
6883 ##      can be overridden by subclasses (eg. IpAddrCtrl.) 
6884 ##  21. Changed a lot of code to be more functionally-oriented so side-effects 
6885 ##      aren't as problematic when maintaining code and/or adding features. 
6886 ##      Eg: IsValid() now does not have side-effects; it merely reflects the 
6887 ##      validity of the value of the control; to determine validity AND recolor 
6888 ##      the control, _CheckValid() should be used with a value argument of None. 
6889 ##      Similarly, made most reformatting function take an optional candidate value 
6890 ##      rather than just using the current value of the control, and only 
6891 ##      have them change the value of the control if a candidate is not specified. 
6892 ##      In this way, you can do validation *before* changing the control. 
6893 ##  22. Changed validRequired to mean "disallow chars that result in invalid 
6894 ##      value."  (Old meaning now represented by emptyInvalid.)  (This was 
6895 ##      possible once I'd made the changes in (19) above.) 
6896 ##  23. Added .SetMaskParameters and .GetMaskParameter methods, so they 
6897 ##      can be set/modified/retrieved after construction.  Removed individual 
6898 ##      parameter setting functions, in favor of this mechanism, so that 
6899 ##      all adjustment of the control based on changing parameter values can 
6900 ##      be handled in one place with unified mechanism. 
6901 ##  24. Did a *lot* of testing and fixing re: numeric values.  Added ability 
6902 ##      to type "grouping char" (ie. ',') and validate as appropriate. 
6903 ##  25. Fixed ZIPPLUS4 to allow either 5 or 4, but if > 5 must be 9. 
6904 ##  26. Fixed assumption about "decimal or integer" masks so that they're only 
6905 ##      made iff there's no validRegex associated with the field.  (This 
6906 ##      is so things like zipcodes which look like integers can have more 
6907 ##      restrictive validation (ie. must be 5 digits.) 
6908 ##  27. Added a ton more doc strings to explain use and derivation requirements 
6909 ##      and did regularization of the naming conventions. 
6910 ##  28. Fixed a range bug in _adjustKey preventing z from being handled properly. 
6911 ##  29. Changed behavior of '.' (and shift-.) in numeric controls to move to 
6912 ##      reformat the value and move the next field as appropriate. (shift-'.', 
6913 ##      ie. '>' moves to the previous field. 
6916 ##   1. Fixed regex bug that caused autoformat AGE to invalidate any age ending 
6918 ##   2. New format character 'D' to trigger date type. If the user enters 2 digits in the 
6919 ##      year position, the control will expand the value to four digits, using numerals below 
6920 ##      50 as 21st century (20+nn) and less than 50 as 20th century (19+nn). 
6921 ##      Also, new optional parameter datestyle = set to one of {MDY|DMY|YDM} 
6922 ##   3. revalid parameter renamed validRegex to conform to standard for all validation 
6923 ##      parameters (see 2 new ones below). 
6924 ##   4. New optional init parameter = validRange. Used only for int/dec (numeric) types. 
6925 ##      Allows the developer to specify a valid low/high range of values. 
6926 ##   5. New optional init parameter = validList. Used for character types. Allows developer 
6927 ##      to send a list of values to the control to be used for specific validation. 
6928 ##      See the Last Name Only example - it is list restricted to Smith/Jones/Williams. 
6929 ##   6. Date type fields now use wxDateTime's parser to validate the date and time. 
6930 ##      This works MUCH better than my kludgy regex!! Thanks to Robin Dunn for pointing 
6931 ##      me toward this solution! 
6932 ##   7. Date fields now automatically expand 2-digit years when it can. For example, 
6933 ##      if the user types "03/10/67", then "67" will auto-expand to "1967". If a two-year 
6934 ##      date is entered it will be expanded in any case when the user tabs out of the 
6936 ##   8. New class functions: SetValidBackgroundColor, SetInvalidBackgroundColor, SetEmptyBackgroundColor, 
6937 ##      SetSignedForeColor allow accessto override default class coloring behavior. 
6938 ##   9. Documentation updated and improved. 
6939 ##  10. Demo - page 2 is now a wxFrame class instead of a wxPyApp class. Works better. 
6940 ##      Two new options (checkboxes) - test highlight empty and disallow empty. 
6941 ##  11. Home and End now work more intuitively, moving to the first and last user-entry 
6942 ##      value, respectively. 
6943 ##  12. New class function: SetRequired(bool). Sets the control's entry required flag 
6944 ##      (i.e. disallow empty values if True). 
6947 ##   1. get_plainValue method renamed to GetPlainValue following the wxWindows 
6948 ##      StudlyCaps(tm) standard (thanks Paul Moore).  ;) 
6949 ##   2. New format code 'F' causes the control to auto-fit (auto-size) itself 
6950 ##      based on the length of the mask template. 
6951 ##   3. Class now supports "autoformat" codes. These can be passed to the class 
6952 ##      on instantiation using the parameter autoformat="code". If the code is in 
6953 ##      the dictionary, it will self set the mask, formatting, and validation string. 
6954 ##      I have included a number of samples, but I am hoping that someone out there 
6955 ##      can help me to define a whole bunch more. 
6956 ##   4. I have added a second page to the demo (as well as a second demo class, test2) 
6957 ##      to showcase how autoformats work. The way they self-format and self-size is, 
6958 ##      I must say, pretty cool. 
6959 ##   5. Comments added and some internal cosmetic revisions re: matching the code 
6960 ##      standards for class submission. 
6961 ##   6. Regex validation is now done in real time - field turns yellow immediately 
6962 ##      and stays yellow until the entered value is valid 
6963 ##   7. Cursor now skips over template characters in a more intuitive way (before the 
6965 ##   8. Change, Keypress and LostFocus methods added for convenience of subclasses. 
6966 ##      Developer may use these methods which will be called after EVT_TEXT, EVT_CHAR, 
6967 ##      and EVT_KILL_FOCUS, respectively. 
6968 ##   9. Decimal and numeric handlers have been rewritten and now work more intuitively. 
6971 ##   1. New .IsEmpty() method returns True if the control's value is equal to the 
6972 ##      blank template string 
6973 ##   2. Control now supports a new init parameter: revalid. Pass a regular expression 
6974 ##      that the value will have to match when the control loses focus. If invalid, 
6975 ##      the control's BackgroundColor will turn yellow, and an internal flag is set (see next). 
6976 ##   3. Demo now shows revalid functionality. Try entering a partial value, such as a 
6977 ##      partial social security number. 
6978 ##   4. New .IsValid() value returns True if the control is empty, or if the value matches 
6979 ##      the revalid expression. If not, .IsValid() returns False. 
6980 ##   5. Decimal values now collapse to decimal with '.00' on losefocus if the user never 
6981 ##      presses the decimal point. 
6982 ##   6. Cursor now goes to the beginning of the field if the user clicks in an 
6983 ##      "empty" field intead of leaving the insertion point in the middle of the 
6985 ##   7. New "N" mask type includes upper and lower chars plus digits. a-zA-Z0-9. 
6986 ##   8. New formatcodes init parameter replaces other init params and adds functions. 
6987 ##      String passed to control on init controls: 
6991 ##        R Show negative #s in red 
6993 ##        - Signed numerals 
6994 ##        0 Numeric fields get leading zeros 
6995 ##   9. Ctrl-X in any field clears the current value. 
6996 ##   10. Code refactored and made more modular (esp in OnChar method). Should be more 
6997 ##       easy to read and understand. 
6998 ##   11. Demo enhanced. 
6999 ##   12. Now has _doc_. 
7002 ##   1. GetPlainValue() now returns the value without the template characters; 
7003 ##      so, for example, a social security number (123-33-1212) would return as 
7004 ##      123331212; also removes white spaces from numeric/decimal values, so 
7005 ##      "-   955.32" is returned "-955.32". Press ctrl-S to see the plain value. 
7006 ##   2. Press '.' in an integer style masked control and truncate any trailing digits. 
7007 ##   3. Code moderately refactored. Internal names improved for clarity. Additional 
7008 ##      internal documentation. 
7009 ##   4. Home and End keys now supported to move cursor to beginning or end of field. 
7010 ##   5. Un-signed integers and decimals now supported. 
7011 ##   6. Cosmetic improvements to the demo. 
7012 ##   7. Class renamed to MaskedTextCtrl. 
7013 ##   8. Can now specify include characters that will override the basic 
7014 ##      controls: for example, includeChars = "@." for email addresses 
7015 ##   9. Added mask character 'C' -> allow any upper or lowercase character 
7016 ##   10. .SetSignColor(str:color) sets the foreground color for negative values 
7017 ##       in signed controls (defaults to red) 
7018 ##   11. Overview documentation written. 
7021 ##   1. Tab now works properly when pressed in last position 
7022 ##   2. Decimal types now work (e.g. #####.##) 
7023 ##   3. Signed decimal or numeric values supported (i.e. negative numbers) 
7024 ##   4. Negative decimal or numeric values now can show in red. 
7025 ##   5. Can now specify an "exclude list" with the excludeChars parameter. 
7026 ##      See date/time formatted example - you can only enter A or P in the 
7027 ##      character mask space (i.e. AM/PM). 
7028 ##   6. Backspace now works properly, including clearing data from a selected 
7029 ##      region but leaving template characters intact. Also delete key. 
7030 ##   7. Left/right arrows now work properly. 
7031 ##   8. Removed EventManager call from test so demo should work with wxPython 2.3.3