1 #----------------------------------------------------------------------------
3 # Authors: Jeff Childers, Will Sadkin
4 # Email: jchilders_98@yahoo.com, wsadkin@nameconnector.com
6 # Copyright: (c) 2003 by Jeff Childers, 2003
7 # Portions: (c) 2002 by Will Sadkin, 2002-2003
9 # License: wxWindows license
10 #----------------------------------------------------------------------------
12 # This was written way it is because of the lack of masked edit controls
13 # in wxWindows/wxPython.
15 # wxMaskedEdit controls are based on a suggestion made on [wxPython-Users] by
16 # Jason Hihn, and borrows liberally from Will Sadkin's original masked edit
17 # control for time entry (wxTimeCtrl).
19 # wxMaskedEdit controls do not normally use validators, because they do
20 # careful manipulation of the cursor in the text window on each keystroke,
21 # and validation is cursor-position specific, so the control intercepts the
22 # key codes before the validator would fire. However, validators can be
23 # provided to do data transfer to the controls.
27 <b>Masked Edit Overview:
28 =====================</b>
29 <b>wxMaskedTextCtrl</b>
30 is a sublassed text control that can carefully control
31 the user's input based on a mask string you provide.
33 General usage example:
34 control = wxMaskedTextCtrl( win, -1, '', mask = '(###) ###-####')
36 The example above will create a text control that allows only numbers to
37 be entered and then only in the positions indicated in the mask by the #
40 <b>wxMaskedComboBox</b>
41 is a similar subclass of wxComboBox that allows the
42 same sort of masking, but also can do auto-complete of values, and can
43 require the value typed to be in the list of choices to be colored
47 is a special subclass of wxMaskedTextCtrl that handles
48 cursor movement and natural typing of IP addresses.
51 <b>INITILIZATION PARAMETERS
52 ========================
54 Allowed mask characters and function:
56 # Allow numeric only (0-9)
57 N Allow letters and numbers (0-9)
58 A Allow uppercase letters only
59 a Allow lowercase letters only
60 C Allow any letter, upper or lower
61 X Allow string.letters, string.punctuation, string.digits
64 These controls define these sets of characters using string.letters,
65 string.uppercase, etc. These sets are affected by the system locale
66 setting, so in order to have the masked controls accept characters
67 that are specific to your users' language, your application should
69 For example, to allow international characters to be used in the
70 above masks, you can place the following in your code as part of
71 your application's initialization code:
74 locale.setlocale(locale.LC_ALL, '')
77 Using these mask characters, a variety of template masks can be built. See
78 the demo for some other common examples include date+time, social security
79 number, etc. If any of these characters are needed as template rather
80 than mask characters, they can be escaped with \, ie. \N means "literal N".
81 (use \\ for literal backslash, as in: r'CCC\\NNN'.)
85 Masks containing only # characters and one optional decimal point
86 character are handled specially, as "numeric" controls. Such
87 controls have special handling for typing the '-' key, handling
88 the "decimal point" character as truncating the ordinal portion,
89 optionally allowing grouping characters and so forth.
90 There are several parameters and format codes that only make sense
91 when combined with such masks, eg. groupChar, decimalChar, and so
92 forth (see below). These allow you to construct reasonable
93 numeric entry controls.
96 Changing the mask for a control deletes any previous field classes
97 (and any associated validation or formatting constraints) for them.
99 <b>useFixedWidthFont=</b>
100 By default, masked edit controls use a fixed width font, so that
101 the mask characters are fixed within the control, regardless of
102 subsequent modifications to the value. Set to False if having
103 the control font be the same as other controls is required.
107 These other properties can be passed to the class when instantiating it:
108 Formatcodes are specified as a string of single character formatting
109 codes that modify behavior of the control:
113 R right-align field(s)
114 r right-insert in field(s) (implies R)
115 < stay in field until explicit navigation out of it
116 , Allow grouping character in integer fields of numeric controls
117 and auto-group/regroup digits (if the result fits) when leaving
118 such a field. (If specified, .SetValue() will attempt to
120 ',' is also the default grouping character. To change the
121 grouping character and/or decimal character, use the groupChar
122 and decimalChar parameters, respectively.
123 Note: typing the "decimal point" character in such fields will
124 clip the value to that left of the cursor for integer
125 fields of controls with "integer" or "floating point" masks.
126 If the ',' format code is specified, this will also cause the
127 resulting digits to be regrouped properly, using the current
129 - Prepend and reserve leading space for sign to mask and allow
130 signed values (negative #s shown in red by default)
131 0 integer fields get leading zeros
134 F Auto-Fit: the control calulates its size from
135 the length of the template mask
136 V validate entered chars against validRegex before allowing them
137 to be entered vs. being allowed by basic mask and then having
138 the resulting value just colored as invalid.
139 (See USSTATE autoformat demo for how this can be used.)
140 S select entire field when navigating to new field
144 These controls have two options for the initial state of the control.
145 If a blank control with just the non-editable characters showing
146 is desired, simply leave the constructor variable fillChar as its
147 default (' '). If you want some other character there, simply
148 change the fillChar to that value. Note: changing the control's fillChar
149 will implicitly reset all of the fields' fillChars to this value.
151 If you need different default characters in each mask position,
152 you can specify a defaultValue parameter in the constructor, or
153 set them for each field individually.
154 This value must satisfy the non-editable characters of the mask,
155 but need not conform to the replaceable characters.
159 These parameters govern what character is used to group numbers
160 and is used to indicate the decimal point for numeric format controls.
161 The default groupChar is ',', the default decimalChar is '.'
162 By changing these, you can customize the presentation of numbers
164 eg: formatcodes = ',', groupChar="'" allows 12'345.34
165 formatcodes = ',', groupChar='.', decimalChar=',' allows 12.345,34
167 <b>shiftDecimalChar=</b>
168 The default "shiftDecimalChar" (used for "backwards-tabbing" until
169 shift-tab is fixed in wxPython) is '>' (for QUERTY keyboards.) for
170 other keyboards, you may want to customize this, eg '?' for shift ',' on
171 AZERTY keyboards, ':' or ';' for other European keyboards, etc.
173 <b>autoCompleteKeycodes=[]</b>
174 By default, DownArrow, PageUp and PageDown will auto-complete a
175 partially entered field. Shift-DownArrow, Shift-UpArrow, PageUp
176 and PageDown will also auto-complete, but if the field already
177 contains a matched value, these keys will cycle through the list
178 of choices forward or backward as appropriate. Shift-Up and
179 Shift-Down also take you to the next/previous field after any
180 auto-complete action.
182 Additional auto-complete keys can be specified via this parameter.
183 Any keys so specified will act like PageDown.
187 <b>Validating User Input:
188 ======================</b>
189 There are a variety of initialization parameters that are used to validate
190 user input. These parameters can apply to the control as a whole, and/or
191 to individual fields:
193 excludeChars= A string of characters to exclude even if otherwise allowed
194 includeChars= A string of characters to allow even if otherwise disallowed
195 validRegex= Use a regular expression to validate the contents of the text box
196 validRange= Pass a rangeas list (low,high) to limit numeric fields/values
197 choiceRequired= value must be member of choices list
198 compareNoCase= Perform case-insensitive matching when validating against list
199 emptyInvalid= Boolean indicating whether an empty value should be considered invalid
201 validFunc= A function to call of the form: bool = func(candidate_value)
202 which will return True if the candidate_value satisfies some
203 external criteria for the control in addition to the the
204 other validation, or False if not. (This validation is
205 applied last in the chain of validations.)
207 validRequired= Boolean indicating whether or not keys that are allowed by the
208 mask, but result in an invalid value are allowed to be entered
209 into the control. Setting this to True implies that a valid
210 default value is set for the control.
212 retainFieldValidation=
213 False by default; if True, this allows individual fields to
214 retain their own validation constraints independently of any
215 subsequent changes to the control's overall parameters.
217 validator= Validators are not normally needed for masked controls, because
218 of the nature of the validation and control of input. However,
219 you can supply one to provide data transfer routines for the
223 <b>Coloring Behavior:
224 ==================</b>
225 The following parameters have been provided to allow you to change the default
226 coloring behavior of the control. These can be set at construction, or via
227 the .SetCtrlParameters() function. Pass a color as string e.g. 'Yellow':
229 emptyBackgroundColor= Control Background color when identified as empty. Default=White
230 invalidBackgroundColor= Control Background color when identified as Not valid. Default=Yellow
231 validBackgroundColor= Control Background color when identified as Valid. Default=white
234 The following parameters control the default foreground color coloring behavior of the
235 control. Pass a color as string e.g. 'Yellow':
236 foregroundColor= Control foreground color when value is not negative. Default=Black
237 signedForegroundColor= Control foreground color when value is negative. Default=Red
242 Each part of the mask that allows user input is considered a field. The fields
243 are represented by their own class instances. You can specify field-specific
244 constraints by constructing or accessing the field instances for the control
245 and then specifying those constraints via parameters.
248 This parameter allows you to specify Field instances containing
249 constraints for the individual fields of a control, eg: local
250 choice lists, validation rules, functions, regexps, etc.
251 It can be either an ordered list or a dictionary. If a list,
252 the fields will be applied as fields 0, 1, 2, etc.
253 If a dictionary, it should be keyed by field index.
254 the values should be a instances of maskededit.Field.
256 Any field not represented by the list or dictionary will be
257 implicitly created by the control.
260 fields = [ Field(formatcodes='_r'), Field('choices=['a', 'b', 'c']) ]
263 1: ( Field(formatcodes='_R', choices=['a', 'b', 'c']),
264 3: ( Field(choices=['01', '02', '03'], choiceRequired=True)
267 The following parameters are available for individual fields, with the
268 same semantics as for the whole control but applied to the field in question:
270 fillChar # if set for a field, it will override the control's fillChar for that field
271 groupChar # if set for a field, it will override the control's default
272 defaultValue # sets field-specific default value; overrides any default from control
273 compareNoCase # overrides control's settings
274 emptyInvalid # determines whether field is required to be filled at all times
275 validRequired # if set, requires field to contain valid value
277 If any of the above parameters are subsequently specified for the control as a
278 whole, that new value will be propagated to each field, unless the
279 retainFieldValidation control-level parameter is set.
281 formatcodes # Augments control's settings
287 choiceRequired # ' ' '
292 <b>Control Class Functions:
293 ========================
294 .GetPlainValue(value=None)</b>
295 Returns the value specified (or the control's text value
296 not specified) without the formatting text.
297 In the example above, might return phone no='3522640075',
298 whereas control.GetValue() would return '(352) 264-0075'
300 Returns the control's value to its default, and places the
301 cursor at the beginning of the control.
303 Does "smart replacement" of passed value into the control, as does
304 the .Paste() method. As with other text entry controls, the
305 .SetValue() text replacement begins at left-edge of the control,
306 with missing mask characters inserted as appropriate.
307 .SetValue will also adjust integer, float or date mask entry values,
308 adding commas, auto-completing years, etc. as appropriate.
309 For "right-aligned" numeric controls, it will also now automatically
310 right-adjust any value whose length is less than the width of the
311 control before attempting to set the value.
312 If a value does not follow the format of the control's mask, or will
313 not fit into the control, a ValueError exception will be raised.
315 mask = '(###) ###-####'
316 .SetValue('1234567890') => '(123) 456-7890'
317 .SetValue('(123)4567890') => '(123) 456-7890'
318 .SetValue('(123)456-7890') => '(123) 456-7890'
319 .SetValue('123/4567-890') => illegal paste; ValueError
321 mask = '#{6}.#{2}', formatcodes = '_,-',
322 .SetValue('111') => ' 111 . '
323 .SetValue(' %9.2f' % -111.12345 ) => ' -111.12'
324 .SetValue(' %9.2f' % 1234.00 ) => ' 1,234.00'
325 .SetValue(' %9.2f' % -1234567.12345 ) => insufficient room; ValueError
327 mask = '#{6}.#{2}', formatcodes = '_,-R' # will right-adjust value for right-aligned control
328 .SetValue('111') => padded value misalignment ValueError: " 111" will not fit
329 .SetValue('%.2f' % 111 ) => ' 111.00'
330 .SetValue('%.2f' % -111.12345 ) => ' -111.12'
333 <b>.IsValid(value=None)</b>
334 Returns True if the value specified (or the value of the control
335 if not specified) passes validation tests
336 <b>.IsEmpty(value=None)</b>
337 Returns True if the value specified (or the value of the control
338 if not specified) is equal to an "empty value," ie. all
339 editable characters == the fillChar for their respective fields.
340 <b>.IsDefault(value=None)</b>
341 Returns True if the value specified (or the value of the control
342 if not specified) is equal to the initial value of the control.
345 Recolors the control as appropriate to its current settings.
347 <b>.SetCtrlParameters(**kwargs)</b>
348 This function allows you to set up and/or change the control parameters
349 after construction; it takes a list of key/value pairs as arguments,
350 where the keys can be any of the mask-specific parameters in the constructor.
352 ctl = wxMaskedTextCtrl( self, -1 )
353 ctl.SetCtrlParameters( mask='###-####',
354 defaultValue='555-1212',
357 <b>.GetCtrlParameter(parametername)</b>
358 This function allows you to retrieve the current value of a parameter
361 <b>.SetFieldParameters(field_index, **kwargs)</b>
362 This function allows you to specify change individual field
363 parameters after construction. (Indices are 0-based.)
365 <b>.GetFieldParameter(field_index, parametername)</b>
366 Allows the retrieval of field parameters after construction
369 The control detects certain common constructions. In order to use the signed feature
370 (negative numbers and coloring), the mask has to be all numbers with optionally one
371 decimal. Without a decimal (e.g. '######', the control will treat it as an integer
372 value. With a decimal (e.g. '###.##'), the control will act as a decimal control
373 (i.e. press decimal to 'tab' to the decimal position). Pressing decimal in the
374 integer control truncates the value.
377 Check your controls by calling each control's .IsValid() function and the
378 .IsEmpty() function to determine which controls have been a) filled in and
379 b) filled in properly.
382 Regular expression validations can be used flexibly and creatively.
383 Take a look at the demo; the zip-code validation succeeds as long as the
384 first five numerals are entered. the last four are optional, but if
385 any are entered, there must be 4 to be valid.
390 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
395 All methods of the Mixin that are not meant to be exposed to the external
396 interface are prefaced with '_'. Those functions that are primarily
397 intended to be internal subroutines subsequently start with a lower-case
398 letter; those that are primarily intended to be used and/or overridden
399 by derived subclasses start with a capital letter.
401 The following methods must be used and/or defined when deriving a control
402 from wxMaskedEditMixin. NOTE: if deriving from a *masked edit* control
403 (eg. class wxIpAddrCtrl(wxMaskedTextCtrl) ), then this is NOT necessary,
404 as it's already been done for you in the base class.
407 This function must be called after the associated base
408 control has been initialized in the subclass __init__
409 function. It sets the initial value of the control,
410 either to the value specified if non-empty, the
411 default value if specified, or the "template" for
412 the empty control as necessary. It will also set/reset
413 the font if necessary and apply formatting to the
414 control at this time.
418 Each class derived from wxMaskedEditMixin must define
419 the function for getting the start and end of the
420 current text selection. The reason for this is
421 that not all controls have the same function name for
422 doing this; eg. wxTextCtrl uses .GetSelection(),
423 whereas we had to write a .GetMark() function for
424 wxComboBox, because .GetSelection() for the control
425 gets the currently selected list item from the combo
426 box, and the control doesn't (yet) natively provide
427 a means of determining the text selection.
430 Similarly to _GetSelection, each class derived from
431 wxMaskedEditMixin must define the function for setting
432 the start and end of the current text selection.
433 (eg. .SetSelection() for wxMaskedTextCtrl, and .SetMark() for
436 ._GetInsertionPoint()
437 ._SetInsertionPoint()
439 For consistency, and because the mixin shouldn't rely
440 on fixed names for any manipulations it does of any of
441 the base controls, we require each class derived from
442 wxMaskedEditMixin to define these functions as well.
445 ._SetValue() REQUIRED
446 Each class derived from wxMaskedEditMixin must define
447 the functions used to get and set the raw value of the
449 This is necessary so that recursion doesn't take place
450 when setting the value, and so that the mixin can
451 call the appropriate function after doing all its
452 validation and manipulation without knowing what kind
453 of base control it was mixed in with.
458 Each class derived from wxMaskedEditMixin must redefine
459 these functions to call the _Cut(), _Paste() and _Paste()
460 methods, respectively for the control, so as to prevent
461 programmatic corruption of the control's value. This
462 must be done in each derivation, as the mixin cannot
463 itself override a member of a sibling class.
466 Each class derived from wxMaskedEditMixin must define
467 the function used to refresh the base control.
470 Each class derived from wxMaskedEditMixin must redefine
471 this function so that it checks the validity of the
472 control (via self._CheckValid) and then refreshes
473 control using the base class method.
475 ._IsEditable() REQUIRED
476 Each class derived from wxMaskedEditMixin must define
477 the function used to determine if the base control is
478 editable or not. (For wxMaskedComboBox, this has to
479 be done with code, rather than specifying the proper
480 function in the base control, as there isn't one...)
486 Event handlers are "chained", and wxMaskedEditMixin usually
487 swallows most of the events it sees, thereby preventing any other
488 handlers from firing in the chain. It is therefore required that
489 each class derivation using the mixin to have an option to hook up
490 the event handlers itself or forego this operation and let a
491 subclass of the masked control do so. For this reason, each
492 subclass should probably include the following code:
494 if setupEventHandling:
495 ## Setup event handlers
496 EVT_SET_FOCUS( self, self._OnFocus ) ## defeat automatic full selection
497 EVT_KILL_FOCUS( self, self._OnKillFocus ) ## run internal validator
498 EVT_LEFT_DCLICK(self, self._OnDoubleClick) ## select field under cursor on dclick
499 EVT_KEY_DOWN( self, self._OnKeyDown ) ## capture control events not normally seen, eg ctrl-tab.
500 EVT_CHAR( self, self._OnChar ) ## handle each keypress
501 EVT_TEXT( self, self.GetId(), self._OnTextChange ) ## color control appropriately
503 where setupEventHandling is an argument to its constructor.
505 These 5 handlers must be "wired up" for the wxMaskedEdit
506 control to provide default behavior. (The setupEventHandling
507 is an argument to wxMaskedTextCtrl and wxMaskedComboBox, so
508 that controls derived from *them* may replace one of these
509 handlers if they so choose.)
511 If your derived control wants to preprocess events before
512 taking action, it should then set up the event handling itself,
513 so it can be first in the event handler chain.
516 The following routines are available to facilitate changing
517 the default behavior of wxMaskedEdit controls:
519 ._SetKeycodeHandler(keycode, func)
520 ._SetKeyHandler(char, func)
521 Use to replace default handling for any given keycode.
522 func should take the key event as argument and return
523 False if no further action is required to handle the
525 self._SetKeycodeHandler(WXK_UP, self.IncrementValue)
526 self._SetKeyHandler('-', self._OnChangeSign)
528 "Navigation" keys are assumed to change the cursor position, and
529 therefore don't cause automatic motion of the cursor as insertable
532 ._AddNavKeycode(keycode, handler=None)
533 ._AddNavKey(char, handler=None)
534 Allows controls to specify other keys (and optional handlers)
535 to be treated as navigational characters. (eg. '.' in wxIpAddrCtrl)
537 ._GetNavKeycodes() Returns the current list of navigational keycodes.
539 ._SetNavKeycodes(key_func_tuples)
540 Allows replacement of the current list of keycode
541 processed as navigation keys, and bind associated
542 optional keyhandlers. argument is a list of key/handler
543 tuples. Passing a value of None for the handler in a
544 given tuple indicates that default processing for the key
547 ._FindField(pos) Returns the Field object associated with this position
550 ._FindFieldExtent(pos, getslice=False, value=None)
551 Returns edit_start, edit_end of the field corresponding
552 to the specified position within the control, and
553 optionally also returns the current contents of that field.
554 If value is specified, it will retrieve the slice the corresponding
555 slice from that value, rather than the current value of the
559 This is, the function that gets called for a given position
560 whenever the cursor is adjusted to leave a given field.
561 By default, it adjusts the year in date fields if mask is a date,
562 It can be overridden by a derived class to
563 adjust the value of the control at that time.
564 (eg. wxIpAddrCtrl reformats the address in this way.)
566 ._Change() Called by internal EVT_TEXT handler. Return False to force
567 skip of the normal class change event.
568 ._Keypress(key) Called by internal EVT_CHAR handler. Return False to force
569 skip of the normal class keypress event.
570 ._LostFocus() Called by internal EVT_KILL_FOCUS handler
573 This is the default EVT_KEY_DOWN routine; it just checks for
574 "navigation keys", and if event.ControlDown(), it fires the
575 mixin's _OnChar() routine, as such events are not always seen
576 by the "cooked" EVT_CHAR routine.
578 ._OnChar(event) This is the main EVT_CHAR handler for the
581 The following routines are used to handle standard actions
583 _OnArrow(event) used for arrow navigation events
584 _OnCtrl_A(event) 'select all'
585 _OnCtrl_S(event) 'save' (does nothing)
586 _OnCtrl_V(event) 'paste' - calls _Paste() method, to do smart paste
587 _OnCtrl_X(event) 'cut' - calls _Cut() method, to "erase" selection
589 _OnChangeField(event) primarily used for tab events, but can be
590 used for other keys (eg. '.' in wxIpAddrCtrl)
592 _OnErase(event) used for backspace and delete
598 from wxPython
.wx
import *
599 import string
, re
, copy
601 from wxPython
.tools
.dbg
import Logger
605 ## ---------- ---------- ---------- ---------- ---------- ---------- ----------
607 ## Constants for identifying control keys and classes of keys:
609 WXK_CTRL_X
= (ord('X')+1) - ord('A') ## These keys are not already defined in wx
610 WXK_CTRL_V
= (ord('V')+1) - ord('A')
611 WXK_CTRL_C
= (ord('C')+1) - ord('A')
612 WXK_CTRL_S
= (ord('S')+1) - ord('A')
613 WXK_CTRL_A
= (ord('A')+1) - ord('A')
615 nav
= (WXK_BACK
, WXK_LEFT
, WXK_RIGHT
, WXK_UP
, WXK_DOWN
, WXK_TAB
, WXK_HOME
, WXK_END
, WXK_RETURN
, WXK_PRIOR
, WXK_NEXT
)
616 control
= (WXK_BACK
, WXK_DELETE
, WXK_CTRL_A
, WXK_CTRL_C
, WXK_CTRL_S
, WXK_CTRL_V
, WXK_CTRL_X
)
619 ## ---------- ---------- ---------- ---------- ---------- ---------- ----------
621 ## Constants for masking. This is where mask characters
623 ## maskchars used to identify valid mask characters from all others
624 ## #- allow numeric 0-9 only
625 ## A- allow uppercase only. Combine with forceupper to force lowercase to upper
626 ## a- allow lowercase only. Combine with forcelower to force upper to lowercase
627 ## X- allow any character (string.letters, string.punctuation, string.digits)
628 ## Note: locale settings affect what "uppercase", lowercase, etc comprise.
630 maskchars
= ("#","A","a","X","C","N")
632 months
= '(01|02|03|04|05|06|07|08|09|10|11|12)'
633 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)'
634 charmonths_dict
= {'jan': 1, 'feb': 2, 'mar': 3, 'apr': 4, 'may': 5, 'jun': 6,
635 'jul': 7, 'aug': 8, 'sep': 9, 'oct': 10, 'nov': 11, 'dec': 12}
637 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)'
638 hours
= '(0\d| \d|1[012])'
639 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)'
640 minutes
= """(00|01|02|03|04|05|06|07|08|09|10|11|12|13|14|15|\
641 16|17|18|19|20|21|22|23|24|25|26|27|28|29|30|31|32|33|34|35|\
642 36|37|38|39|40|41|42|43|44|45|46|47|48|49|50|51|52|53|54|55|\
645 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'
647 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(',')
650 ## ---------- ---------- ---------- ---------- ---------- ---------- ----------
652 ## The following table defines the current set of autoformat codes:
655 # Name: (mask, excludeChars, formatcodes, validRegex, choices, choiceRequired, description)
656 "USPHONEFULLEXT":("(###) ###-#### x:###","",'F^-R',"^\(\d{3}\) \d{3}-\d{4}",[], False, "Phone Number w/opt. ext"),
657 "USPHONETIGHTEXT":("###-###-#### x:###","",'F^-R',"^\d{3}-\d{3}-\d{4}",[], False, "Phone Number\n (w/hyphens and opt. ext)"),
658 "USPHONEFULL":("(###) ###-####","",'F^-R',"^\(\d{3}\) \d{3}-\d{4}",[], False, "Phone Number only"),
659 "USPHONETIGHT":("###-###-####","",'F^-R',"^\d{3}-\d{3}-\d{4}",[], False, "Phone Number\n(w/hyphens)"),
660 "USSTATE":("AA","",'F!V',"([ACDFGHIKLMNOPRSTUVW] |%s)" % string
.join(states
,'|'), states
, True, "US State"),
662 "USDATETIMEMMDDYYYY/HHMMSS":("##/##/#### ##:##:## AM",am_pm_exclude
,'DF!','^' + months
+ '/' + days
+ '/' + '\d{4} ' + hours
+ ':' + minutes
+ ':' + seconds
+ ' (A|P)M',[], False, "US Date + Time"),
663 "USDATETIMEMMDDYYYY-HHMMSS":("##-##-#### ##:##:## AM",am_pm_exclude
,'DF!','^' + months
+ '-' + days
+ '-' + '\d{4} ' + hours
+ ':' + minutes
+ ':' + seconds
+ ' (A|P)M',[], False, "US Date + Time\n(w/hypens)"),
664 "USDATEMILTIMEMMDDYYYY/HHMMSS":("##/##/#### ##:##:##",'','DF','^' + months
+ '/' + days
+ '/' + '\d{4} ' + milhours
+ ':' + minutes
+ ':' + seconds
,[], False, "US Date + Military Time"),
665 "USDATEMILTIMEMMDDYYYY-HHMMSS":("##-##-#### ##:##:##",'','DF','^' + months
+ '-' + days
+ '-' + '\d{4} ' + milhours
+ ':' + minutes
+ ':' + seconds
,[], False, "US Date + Military Time\n(w/hypens)"),
666 "USDATETIMEMMDDYYYY/HHMM":("##/##/#### ##:## AM",am_pm_exclude
,'DF!','^' + months
+ '/' + days
+ '/' + '\d{4} ' + hours
+ ':' + minutes
+ ' (A|P)M',[], False, "US Date + Time\n(without seconds)"),
667 "USDATEMILTIMEMMDDYYYY/HHMM":("##/##/#### ##:##",'','DF','^' + months
+ '/' + days
+ '/' + '\d{4} ' + milhours
+ ':' + minutes
,[], False, "US Date + Military Time\n(without seconds)"),
668 "USDATETIMEMMDDYYYY-HHMM":("##-##-#### ##:## AM",am_pm_exclude
,'DF!','^' + months
+ '-' + days
+ '-' + '\d{4} ' + hours
+ ':' + minutes
+ ' (A|P)M',[], False, "US Date + Time\n(w/hypens and w/o secs)"),
669 "USDATEMILTIMEMMDDYYYY-HHMM":("##-##-#### ##:##",'','DF','^' + months
+ '-' + days
+ '-' + '\d{4} ' + milhours
+ ':' + minutes
,[], False, "US Date + Military Time\n(w/hyphens and w/o seconds)"),
670 "USDATEMMDDYYYY/":("##/##/####",'','DF','^' + months
+ '/' + days
+ '/' + '\d{4}',[], False, "US Date\n(MMDDYYYY)"),
671 "USDATEMMDDYY/":("##/##/##",'','DF','^' + months
+ '/' + days
+ '/\d\d',[], False, "US Date\n(MMDDYY)"),
672 "USDATEMMDDYYYY-":("##-##-####",'','DF','^' + months
+ '-' + days
+ '-' +'\d{4}',[], False, "MM-DD-YYYY"),
674 "EUDATEYYYYMMDD/":("####/##/##",'','DF','^' + '\d{4}'+ '/' + months
+ '/' + days
,[], False, "YYYY/MM/DD"),
675 "EUDATEYYYYMMDD.":("####.##.##",'','DF','^' + '\d{4}'+ '.' + months
+ '.' + days
,[], False, "YYYY.MM.DD"),
676 "EUDATEDDMMYYYY/":("##/##/####",'','DF','^' + days
+ '/' + months
+ '/' + '\d{4}',[], False, "DD/MM/YYYY"),
677 "EUDATEDDMMYYYY.":("##.##.####",'','DF','^' + days
+ '.' + months
+ '.' + '\d{4}',[], False, "DD.MM.YYYY"),
678 "EUDATEDDMMMYYYY.":("##.CCC.####",'','DF','^' + days
+ '.' + charmonths
+ '.' + '\d{4}',[], False, "DD.Month.YYYY"),
679 "EUDATEDDMMMYYYY/":("##/CCC/####",'','DF','^' + days
+ '/' + charmonths
+ '/' + '\d{4}',[], False, "DD/Month/YYYY"),
681 "EUDATETIMEYYYYMMDD/HHMMSS":("####/##/## ##:##:## AM",am_pm_exclude
,'DF!','^' + '\d{4}'+ '/' + months
+ '/' + days
+ ' ' + hours
+ ':' + minutes
+ ':' + seconds
+ ' (A|P)M',[], False, "YYYY/MM/DD HH:MM:SS"),
682 "EUDATETIMEYYYYMMDD.HHMMSS":("####.##.## ##:##:## AM",am_pm_exclude
,'DF!','^' + '\d{4}'+ '.' + months
+ '.' + days
+ ' ' + hours
+ ':' + minutes
+ ':' + seconds
+ ' (A|P)M',[], False, "YYYY.MM.DD HH:MM:SS"),
683 "EUDATETIMEDDMMYYYY/HHMMSS":("##/##/#### ##:##:## AM",am_pm_exclude
,'DF!','^' + days
+ '/' + months
+ '/' + '\d{4} ' + hours
+ ':' + minutes
+ ':' + seconds
+ ' (A|P)M',[], False, "DD/MM/YYYY HH:MM:SS"),
684 "EUDATETIMEDDMMYYYY.HHMMSS":("##.##.#### ##:##:## AM",am_pm_exclude
,'DF!','^' + days
+ '.' + months
+ '.' + '\d{4} ' + hours
+ ':' + minutes
+ ':' + seconds
+ ' (A|P)M',[], False, "DD.MM.YYYY HH:MM:SS"),
686 "EUDATETIMEYYYYMMDD/HHMM":("####/##/## ##:## AM",am_pm_exclude
,'DF!','^' + '\d{4}'+ '/' + months
+ '/' + days
+ ' ' + hours
+ ':' + minutes
+ ' (A|P)M',[], False, "YYYY/MM/DD HH:MM"),
687 "EUDATETIMEYYYYMMDD.HHMM":("####.##.## ##:## AM",am_pm_exclude
,'DF!','^' + '\d{4}'+ '.' + months
+ '.' + days
+ ' ' + hours
+ ':' + minutes
+ ' (A|P)M',[], False, "YYYY.MM.DD HH:MM"),
688 "EUDATETIMEDDMMYYYY/HHMM":("##/##/#### ##:## AM",am_pm_exclude
,'DF!','^' + days
+ '/' + months
+ '/' + '\d{4} ' + hours
+ ':' + minutes
+ ' (A|P)M',[], False, "DD/MM/YYYY HH:MM"),
689 "EUDATETIMEDDMMYYYY.HHMM":("##.##.#### ##:## AM",am_pm_exclude
,'DF!','^' + days
+ '.' + months
+ '.' + '\d{4} ' + hours
+ ':' + minutes
+ ' (A|P)M',[], False, "DD.MM.YYYY HH:MM"),
691 "EUDATEMILTIMEYYYYMMDD/HHMMSS":("####/##/## ##:##:##",'','DF','^' + '\d{4}'+ '/' + months
+ '/' + days
+ ' ' + milhours
+ ':' + minutes
+ ':' + seconds
,[], False, "YYYY/MM/DD Mil. Time"),
692 "EUDATEMILTIMEYYYYMMDD.HHMMSS":("####.##.## ##:##:##",'','DF','^' + '\d{4}'+ '.' + months
+ '.' + days
+ ' ' + milhours
+ ':' + minutes
+ ':' + seconds
,[], False, "YYYY.MM.DD Mil. Time"),
693 "EUDATEMILTIMEDDMMYYYY/HHMMSS":("##/##/#### ##:##:##",'','DF','^' + days
+ '/' + months
+ '/' + '\d{4} ' + milhours
+ ':' + minutes
+ ':' + seconds
,[], False, "DD/MM/YYYY Mil. Time"),
694 "EUDATEMILTIMEDDMMYYYY.HHMMSS":("##.##.#### ##:##:##",'','DF','^' + days
+ '.' + months
+ '.' + '\d{4} ' + milhours
+ ':' + minutes
+ ':' + seconds
,[], False, "DD.MM.YYYY Mil. Time"),
695 "EUDATEMILTIMEYYYYMMDD/HHMM":("####/##/## ##:##",'','DF','^' + '\d{4}'+ '/' + months
+ '/' + days
+ ' ' + milhours
+ ':' + minutes
,[], False, "YYYY/MM/DD Mil. Time\n(w/o seconds)"),
696 "EUDATEMILTIMEYYYYMMDD.HHMM":("####.##.## ##:##",'','DF','^' + '\d{4}'+ '.' + months
+ '.' + days
+ ' ' + milhours
+ ':' + minutes
,[], False, "YYYY.MM.DD Mil. Time\n(w/o seconds)"),
697 "EUDATEMILTIMEDDMMYYYY/HHMM":("##/##/#### ##:##",'','DF','^' + days
+ '/' + months
+ '/' + '\d{4} ' + milhours
+ ':' + minutes
,[], False, "DD/MM/YYYY Mil. Time\n(w/o seconds)"),
698 "EUDATEMILTIMEDDMMYYYY.HHMM":("##.##.#### ##:##",'','DF','^' + days
+ '.' + months
+ '.' + '\d{4} ' + milhours
+ ':' + minutes
,[], False, "DD.MM.YYYY Mil. Time\n(w/o seconds)"),
700 "TIMEHHMMSS":("##:##:## AM", am_pm_exclude
, 'TF!', '^' + hours
+ ':' + minutes
+ ':' + seconds
+ ' (A|P)M',[], False, "HH:MM:SS (A|P)M\n(see wxTimeCtrl)"),
701 "TIMEHHMM":("##:## AM", am_pm_exclude
, 'TF!', '^' + hours
+ ':' + minutes
+ ' (A|P)M',[], False, "HH:MM (A|P)M\n(see wxTimeCtrl)"),
702 "MILTIMEHHMMSS":("##:##:##", "", 'TF', '^' + milhours
+ ':' + minutes
+ ':' + seconds
,[], False, "Military HH:MM:SS\n(see wxTimeCtrl)"),
703 "MILTIMEHHMM":("##:##", "", 'TF', '^' + milhours
+ ':' + minutes
,[], False, "Military HH:MM\n(see wxTimeCtrl)"),
704 "USSOCIALSEC":("###-##-####","",'F',"\d{3}-\d{2}-\d{4}",[], False, "Social Sec#"),
705 "CREDITCARD":("####-####-####-####","",'F',"\d{4}-\d{4}-\d{4}-\d{4}",[], False, "Credit Card"),
706 "EXPDATEMMYY":("##/##", "", "F", "^" + months
+ "/\d\d",[], False, "Expiration MM/YY"),
707 "USZIP":("#####","",'F',"^\d{5}",[], False, "US 5-digit zip code"),
708 "USZIPPLUS4":("#####-####","",'F',"\d{5}-(\s{4}|\d{4})",[], False, "US zip+4 code"),
709 "PERCENT":("0.##","",'F',"^0.\d\d",[], False, "Percentage"),
710 "AGE":("###","","F","^[1-9]{1} |[1-9][0-9] |1[0|1|2][0-9]",[], False, "Age"),
711 "EMAIL":("XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"," \\/*&%$#!+='\"","F",
712 "[a-zA-Z]\w*(\.\w+)*@\w+\.([a-zA-Z]\w*\.)*(com|org|net|edu|mil|gov|(co\.)?\w\w) *$",[], False, "Email address"),
713 "IPADDR":("###.###.###.###", "", 'F_Sr<',
714 "( \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}",[], False, "IP Address\n(see wxIpAddrCtrl)")
717 # build demo-friendly dictionary of descriptions of autoformats
719 for key
, value
in masktags
.items():
720 autoformats
.append((key
, value
[6]))
723 ## ---------- ---------- ---------- ---------- ---------- ---------- ----------
727 'index': None, ## which field of mask; set by parent control.
728 'mask': "", ## mask chars for this field
729 'extent': (), ## (edit start, edit_end) of field; set by parent control.
730 'formatcodes': "", ## codes indicating formatting options for the control
731 'fillChar': ' ', ## used as initial value for each mask position if initial value is not given
732 'groupChar': ',', ## used with numeric fields; indicates what char groups 3-tuple digits
733 'decimalChar': '.', ## used with numeric fields; indicates what char separates ordinal from fraction
734 'shiftDecimalChar': '>', ## used with numeric fields, indicates what is above the decimal point char on keyboard
735 'defaultValue': "", ## use if you want different positional defaults vs. all the same fillChar
736 'excludeChars': "", ## optional string of chars to exclude even if main mask type does
737 'includeChars': "", ## optional string of chars to allow even if main mask type doesn't
738 'validRegex': "", ## optional regular expression to use to validate the control
739 'validRange': (), ## Optional hi-low range for numerics
740 'choices': [], ## Optional list for character expressions
741 'choiceRequired': False, ## If choices supplied this specifies if valid value must be in the list
742 'validFunc': None, ## Optional function for defining additional, possibly dynamic validation constraints on contrl
743 'compareNoCase': False, ## Optional flag to indicate whether or not to use case-insensitive list search
744 'validRequired': False, ## Set to True to disallow input that results in an invalid value
745 'emptyInvalid': False, ## Set to True to make EMPTY = INVALID
749 def __init__(self
, **kwargs
):
751 This is the "constructor" for setting up parameters for fields.
752 a field_index of -1 is used to indicate "the entire control."
754 ## dbg('Field::Field', indent=1)
755 # Validate legitimate set of parameters:
756 for key
in kwargs
.keys():
757 if key
not in Field
.valid_params
.keys():
758 raise TypeError('invalid parameter "%s"' % (key
))
760 # Set defaults for each parameter for this instance, and fully
761 # populate initial parameter list for configuration:
762 for key
, value
in Field
.valid_params
.items():
763 setattr(self
, '_' + key
, copy
.copy(value
))
764 if not kwargs
.has_key(key
):
765 kwargs
[key
] = copy
.copy(value
)
767 self
._SetParameters
(**kwargs
)
772 def _SetParameters(self
, **kwargs
):
774 This function can be used to set individual or multiple parameters for
775 a masked edit field parameter after construction.
778 dbg('maskededit.Field::_SetParameters', indent
=1)
779 # Validate keyword arguments:
780 for key
in kwargs
.keys():
781 if key
not in Field
.valid_params
.keys():
782 raise AttributeError('invalid keyword argument "%s"' % key
)
784 if self
._index
is not None: dbg('field index:', self
._index
)
785 dbg('parameters:', indent
=1)
786 for key
, value
in kwargs
.items():
787 dbg('%s:' % key
, value
)
790 # First, Assign all parameters specified:
791 for key
in Field
.valid_params
.keys():
792 if kwargs
.has_key(key
):
793 setattr(self
, '_' + key
, kwargs
[key
] )
795 # Now go do validation, semantic and inter-dependency parameter processing:
796 if kwargs
.has_key('choiceRequired'): # (set/changed)
797 if self
._choiceRequired
:
798 self
._choices
= [choice
.strip() for choice
in self
._choices
]
800 if kwargs
.has_key('compareNoCase'): # (set/changed)
801 if self
._compareNoCase
and self
._choices
:
802 self
._choices
= [item
.lower() for item
in self
._choices
]
803 dbg('modified choices:', self
._choices
)
805 if kwargs
.has_key('formatcodes'): # (set/changed)
806 self
._forceupper
= '!' in self
._formatcodes
807 self
._forcelower
= '^' in self
._formatcodes
808 self
._groupdigits
= ',' in self
._formatcodes
809 self
._okSpaces
= '_' in self
._formatcodes
810 self
._padZero
= '0' in self
._formatcodes
811 self
._autofit
= 'F' in self
._formatcodes
812 self
._insertRight
= 'r' in self
._formatcodes
813 self
._alignRight
= 'R' in self
._formatcodes
or 'r' in self
._formatcodes
814 self
._moveOnFieldFull
= not '<' in self
._formatcodes
815 self
._selectOnFieldEntry
= 'S' in self
._formatcodes
817 if self
._groupdigits
:
818 if kwargs
.has_key('groupChar'):
819 self
._groupChar
= kwargs
['groupChar']
820 if kwargs
.has_key('decimalChar'):
821 self
._decimalChar
= kwargs
['decimalChar']
822 if kwargs
.has_key('shiftDecimalChar'):
823 self
._shiftDecimalChar
= kwargs
['shiftDecimalChar']
825 if kwargs
.has_key('formatcodes') or kwargs
.has_key('validRegex'):
826 self
._regexMask
= 'V' in self
._formatcodes
and self
._validRegex
828 if kwargs
.has_key('validRegex'): # (set/changed)
831 if self
._compareNoCase
:
832 self
._filter
= re
.compile(self
._validRegex
, re
.IGNORECASE
)
834 self
._filter
= re
.compile(self
._validRegex
)
836 raise TypeError('%s: validRegex "%s" not a legal regular expression' % (str(self
._index
), self
._validRegex
))
840 if kwargs
.has_key('mask') or kwargs
.has_key('validRegex'): # (set/changed)
841 self
._isInt
= isInteger(self
._mask
)
842 dbg('isInt?', self
._isInt
)
844 if kwargs
.has_key('validRange'): # (set/changed)
845 self
._hasRange
= False
849 if type(self
._validRange
) != type(()) or len( self
._validRange
)!= 2 or self
._validRange
[0] >= self
._validRange
[1]:
850 raise TypeError('%s: validRange %s parameter must be tuple of form (a,b) where a < b'
851 % (str(self
._index
), repr(self
._validRange
)) )
853 self
._hasRange
= True
854 self
._rangeLow
= self
._validRange
[0]
855 self
._rangeHigh
= self
._validRange
[1]
857 if kwargs
.has_key('choices'): # (set/changed)
858 self
._hasList
= False
859 if type(self
._choices
) not in (type(()), type([])):
860 raise TypeError('%s: choices must be a sequence of strings' % str(self
._index
))
861 elif len( self
._choices
) > 0:
862 for choice
in self
._choices
:
863 if type(choice
) != type(''):
864 raise TypeError('%s: choices must be a sequence of strings' % str(self
._index
))
866 # Verify each choice specified is valid:
867 for choice
in self
._choices
:
868 if not self
.IsValid(choice
):
869 raise ValueError('%s: "%s" is not a valid value for the control as specified.' % (str(self
._index
), choice
))
872 # reset field validity assumption:
874 dbg(indent
=0, suspend
=0)
877 def _GetParameter(self
, paramname
):
879 Routine for retrieving the value of any given parameter
881 if Field
.valid_params
.has_key(paramname
):
882 return getattr(self
, '_' + paramname
)
884 TypeError('Field._GetParameter: invalid parameter "%s"' % key
)
887 def IsEmpty(self
, slice):
889 Indicates whether the specified slice is considered empty for the
892 dbg('Field::IsEmpty("%s")' % slice, indent
=1)
893 if not hasattr(self
, '_template'):
894 raise AttributeError('_template')
896 dbg('self._template: "%s"' % self
._template
)
897 dbg('self._defaultValue: "%s"' % str(self
._defaultValue
))
898 if slice == self
._template
and not self
._defaultValue
:
902 elif slice == self
._template
:
904 for pos
in range(len(self
._template
)):
905 ## dbg('slice[%(pos)d] != self._fillChar?' %locals(), slice[pos] != self._fillChar[pos])
906 if slice[pos
] not in (' ', self
._fillChar
):
909 dbg("IsEmpty? %(empty)d (do all mask chars == fillChar?)" % locals(), indent
=0)
912 dbg("IsEmpty? 0 (slice doesn't match template)", indent
=0)
916 def IsValid(self
, slice):
918 Indicates whether the specified slice is considered a valid value for the
922 dbg('Field[%s]::IsValid("%s")' % (str(self
._index
), slice), indent
=1)
923 valid
= True # assume true to start
925 if self
._emptyInvalid
and self
.IsEmpty(slice):
928 elif self
._hasList
and self
._choiceRequired
:
929 dbg("(member of list required)")
930 # do case-insensitive match on list; strip surrounding whitespace from slice (already done for choices):
931 compareStr
= slice.strip()
932 if self
._compareNoCase
:
933 compareStr
= compareStr
.lower()
934 valid
= (compareStr
in self
._choices
)
936 elif self
._hasRange
and not self
.IsEmpty(slice):
937 dbg('validating against range')
939 valid
= self
._rangeLow
<= int(slice) <= self
._rangeHigh
943 elif self
._validRegex
and self
._filter
:
944 dbg('validating against regex')
945 valid
= (re
.match( self
._filter
, slice) is not None)
947 if valid
and self
._validFunc
:
948 dbg('validating against supplied function')
949 valid
= self
._validFunc
(slice)
950 dbg('valid?', valid
, indent
=0, suspend
=0)
954 def _AdjustField(self
, slice):
955 """ 'Fixes' an integer field. Right or left-justifies, as required."""
956 dbg('Field::_AdjustField("%s")' % slice, indent
=1)
957 length
= len(self
._mask
)
959 signpos
= slice.find('-')
960 intStr
= slice.replace( '-', '' ) # drop sign, if any
961 intStr
= intStr
.replace(' ', '') # drop extra spaces
962 intStr
= string
.replace(intStr
,self
._fillChar
,"") # drop extra fillchars
963 intStr
= string
.replace(intStr
,"-","") # drop sign, if any
964 intStr
= string
.replace(intStr
, self
._groupChar
, "") # lose commas/dots
965 if self
._groupdigits
:
968 for i
in range(len(intStr
)-1, -1, -1):
969 new
= intStr
[i
] + new
971 new
= self
._groupChar
+ new
973 if new
and new
[0] == self
._groupChar
:
975 if len(new
) <= length
:
976 # expanded string will still fit and leave room for sign:
978 # else... leave it without the commas...
980 dbg('padzero?', self
._padZero
)
981 dbg('len(intStr):', len(intStr
), 'field length:', length
)
982 if self
._padZero
and len(intStr
) < length
:
983 intStr
= '0' * (length
- len(intStr
)) + intStr
985 intStr
= '-' + intStr
[1:]
987 intStr
= '-' + intStr
990 slice = slice.strip() # drop extra spaces
992 if self
._alignRight
: ## Only if right-alignment is enabled
993 slice = slice.rjust( length
)
995 slice = slice.ljust( length
)
996 dbg('adjusted slice: "%s"' % slice, indent
=0)
1000 ## ---------- ---------- ---------- ---------- ---------- ---------- ----------
1002 class wxMaskedEditMixin
:
1004 This class allows us to abstract the masked edit functionality that could
1005 be associated with any text entry control. (eg. wxTextCtrl, wxComboBox, etc.)
1007 valid_ctrl_params
= {
1008 'mask': 'XXXXXXXXXXXXX', ## mask string for formatting this control
1009 'autoformat': "", ## optional auto-format code to set format from masktags dictionary
1010 'fields': {}, ## optional list/dictionary of maskededit.Field class instances, indexed by position in mask
1011 'datestyle': 'MDY', ## optional date style for date-type values. Can trigger autocomplete year
1012 'autoCompleteKeycodes': [], ## Optional list of additional keycodes which will invoke field-auto-complete
1013 'useFixedWidthFont': True, ## Use fixed-width font instead of default for base control
1014 'retainFieldValidation': False, ## Set this to true if setting control-level parameters independently,
1015 ## from field validation constraints
1016 'emptyBackgroundColor': "White",
1017 'validBackgroundColor': "White",
1018 'invalidBackgroundColor': "Yellow",
1019 'foregroundColor': "Black",
1020 'signedForegroundColor': "Red",
1024 def __init__(self
, name
= 'wxMaskedEdit', **kwargs
):
1026 This is the "constructor" for setting up the mixin variable parameters for the composite class.
1031 # set up flag for doing optional things to base control if possible
1032 if not hasattr(self
, 'controlInitialized'):
1033 self
.controlInitialized
= False
1035 # Set internal state var for keeping track of whether or not a character
1036 # action results in a modification of the control, since .SetValue()
1037 # doesn't modify the base control's internal state:
1038 self
.modified
= False
1040 # Validate legitimate set of parameters:
1041 for key
in kwargs
.keys():
1042 if key
not in wxMaskedEditMixin
.valid_ctrl_params
.keys() + Field
.valid_params
.keys():
1043 raise TypeError('%s: invalid parameter "%s"' % (name
, key
))
1045 ## Set up dictionary that can be used by subclasses to override or add to default
1046 ## behavior for individual characters. Derived subclasses needing to change
1047 ## default behavior for keys can either redefine the default functions for the
1048 ## common keys or add functions for specific keys to this list. Each function
1049 ## added should take the key event as argument, and return False if the key
1050 ## requires no further processing.
1052 ## Initially populated with navigation and function control keys:
1053 self
._keyhandlers
= {
1054 # default navigation keys and handlers:
1055 WXK_BACK
: self
._OnErase
,
1056 WXK_LEFT
: self
._OnArrow
,
1057 WXK_RIGHT
: self
._OnArrow
,
1058 WXK_UP
: self
._OnAutoCompleteField
,
1059 WXK_DOWN
: self
._OnAutoCompleteField
,
1060 WXK_TAB
: self
._OnChangeField
,
1061 WXK_HOME
: self
._OnHome
,
1062 WXK_END
: self
._OnEnd
,
1063 WXK_RETURN
: self
._OnReturn
,
1064 WXK_PRIOR
: self
._OnAutoCompleteField
,
1065 WXK_NEXT
: self
._OnAutoCompleteField
,
1067 # default function control keys and handlers:
1068 WXK_DELETE
: self
._OnErase
,
1069 WXK_CTRL_A
: self
._OnCtrl
_A
,
1070 WXK_CTRL_C
: self
._baseCtrlEventHandler
,
1071 WXK_CTRL_S
: self
._OnCtrl
_S
,
1072 WXK_CTRL_V
: self
._OnCtrl
_V
,
1073 WXK_CTRL_X
: self
._OnCtrl
_X
,
1076 ## bind standard navigational and control keycodes to this instance,
1077 ## so that they can be augmented and/or changed in derived classes:
1078 self
._nav
= list(nav
)
1079 self
._control
= list(control
)
1081 ## Dynamically evaluate and store string constants for mask chars
1082 ## so that locale settings can be made after this module is imported
1083 ## and the controls created after that is done can allow the
1084 ## appropriate characters:
1085 self
.maskchardict
= {
1087 "A": string
.uppercase
,
1088 "a": string
.lowercase
,
1089 "X": string
.letters
+ string
.punctuation
+ string
.digits
,
1090 "C": string
.letters
,
1091 "N": string
.letters
+ string
.digits
1094 ## self._ignoreChange is used by wxMaskedComboBox, because
1095 ## of the hack necessary to determine the selection; it causes
1096 ## EVT_TEXT messages from the combobox to be ignored if set.
1097 self
._ignoreChange
= False
1098 self
._oldvalue
= None
1102 # Set defaults for each parameter for this instance, and fully
1103 # populate initial parameter list for configuration:
1104 for key
, value
in wxMaskedEditMixin
.valid_ctrl_params
.items():
1105 setattr(self
, '_' + key
, copy
.copy(value
))
1106 if not kwargs
.has_key(key
):
1107 ## dbg('%s: "%s"' % (key, repr(value)))
1108 kwargs
[key
] = copy
.copy(value
)
1110 # Create a "field" that holds global parameters for control constraints
1111 self
._ctrl
_constraints
= self
._fields
[-1] = Field(index
=-1)
1112 self
.SetCtrlParameters(**kwargs
)
1116 def SetCtrlParameters(self
, **kwargs
):
1118 This public function can be used to set individual or multiple masked edit
1119 parameters after construction.
1121 dbg('wxMaskedEditMixin::SetCtrlParameters', indent
=1)
1122 dbg('kwargs:', indent
=1)
1123 for key
, value
in kwargs
.items():
1124 dbg(key
, '=', value
)
1127 # Validate keyword arguments:
1128 constraint_kwargs
= {}
1130 for key
, value
in kwargs
.items():
1131 if key
not in wxMaskedEditMixin
.valid_ctrl_params
.keys() + Field
.valid_params
.keys():
1132 raise TypeError('%s: invalid keyword argument "%s"' % (self
.name
, key
))
1133 elif key
in Field
.valid_params
.keys():
1134 constraint_kwargs
[key
] = value
1136 ctrl_kwargs
[key
] = value
1141 if ctrl_kwargs
.has_key('autoformat'):
1142 autoformat
= ctrl_kwargs
['autoformat']
1146 if autoformat
!= self
._autoformat
and autoformat
in masktags
.keys():
1147 dbg('autoformat:', autoformat
)
1148 self
._autoformat
= autoformat
1149 mask
= masktags
[self
._autoformat
][0]
1150 constraint_kwargs
['excludeChars'] = masktags
[self
._autoformat
][1]
1151 constraint_kwargs
['formatcodes'] = masktags
[self
._autoformat
][2]
1152 constraint_kwargs
['validRegex'] = masktags
[self
._autoformat
][3]
1153 constraint_kwargs
['choices'] = masktags
[self
._autoformat
][4]
1154 if masktags
[self
._autoformat
][4]:
1155 constraint_kwargs
['choiceRequired'] = masktags
[self
._autoformat
][5]
1158 dbg('autoformat not selected')
1159 if kwargs
.has_key('mask'):
1160 mask
= kwargs
['mask']
1163 ## Assign style flags
1165 dbg('preserving previous mask')
1166 mask
= self
._previous
_mask
# preserve previous mask
1169 reset_args
['reset_mask'] = mask
1170 constraint_kwargs
['mask'] = mask
1172 # wipe out previous fields; preserve new control-level constraints
1173 self
._fields
= {-1: self._ctrl_constraints}
1176 if ctrl_kwargs
.has_key('fields'):
1177 # do field parameter type validation, and conversion to internal dictionary
1179 fields
= ctrl_kwargs
['fields']
1180 if type(fields
) in (types
.ListType
, types
.TupleType
):
1181 for i
in range(len(fields
)):
1183 if not isinstance(field
, Field
):
1185 raise AttributeError('invalid type for field parameter: %s' % repr(field
))
1186 self
._fields
[i
] = field
1188 elif type(fields
) == types
.DictionaryType
:
1189 for index
, field
in fields
.items():
1190 if not isinstance(field
, Field
):
1192 raise AttributeError('invalid type for field parameter: %s' % repr(field
))
1193 self
._fields
[index
] = field
1196 raise AttributeError('fields parameter must be a list or dictionary; not %s' % repr(fields
))
1198 # Assign constraint parameters for entire control:
1199 ## dbg('control constraints:', indent=1)
1200 ## for key, value in constraint_kwargs.items():
1201 ## dbg('%s:' % key, value)
1204 # determine if changing parameters that should affect the entire control:
1205 for key
in wxMaskedEditMixin
.valid_ctrl_params
.keys():
1206 if key
in ( 'mask', 'fields' ): continue # (processed separately)
1207 if ctrl_kwargs
.has_key(key
):
1208 setattr(self
, '_' + key
, ctrl_kwargs
[key
])
1211 dbg('self._retainFieldValidation:', self
._retainFieldValidation
)
1212 if not self
._retainFieldValidation
:
1213 # Build dictionary of any changing parameters which should be propagated to the
1215 for arg
in ('fillChar', 'groupChar', 'compareNoCase', 'defaultValue', 'validRequired'):
1216 dbg('kwargs.has_key(%s)?' % arg
, kwargs
.has_key(arg
))
1217 dbg('getattr(self._ctrl_constraints, _%s)?' % arg
, getattr(self
._ctrl
_constraints
, '_'+arg
))
1218 reset_args
[arg
] = kwargs
.has_key(arg
) and kwargs
[arg
] != getattr(self
._ctrl
_constraints
, '_'+arg
)
1219 dbg('reset_args[%s]?' % arg
, reset_args
[arg
])
1221 # Set the control-level constraints:
1222 self
._ctrl
_constraints
._SetParameters
(**constraint_kwargs
)
1224 # This routine does the bulk of the interdependent parameter processing, determining
1225 # the field extents of the mask if changed, resetting parameters as appropriate,
1226 # determining the overall template value for the control, etc.
1227 self
._configure
(mask
, **reset_args
)
1229 self
._autofit
= self
._ctrl
_constraints
._autofit
1232 self
._isDate
= 'D' in self
._ctrl
_constraints
._formatcodes
and isDateType(mask
)
1233 self
._isTime
= 'T' in self
._ctrl
_constraints
._formatcodes
and isTimeType(mask
)
1235 # Set _dateExtent, used in date validation to locate date in string;
1236 # always set as though year will be 4 digits, even if mask only has
1237 # 2 digits, so we can always properly process the intended year for
1238 # date validation (leap years, etc.)
1239 if self
._mask
.find('CCC') != -1: self
._dateExtent
= 11
1240 else: self
._dateExtent
= 10
1242 self
._4digityear
= len(self
._mask
) > 8 and self
._mask
[9] == '#'
1244 if self
._isDate
and self
._autoformat
:
1245 # Auto-decide datestyle:
1246 if self
._autoformat
.find('MDDY') != -1: self
._datestyle
= 'MDY'
1247 elif self
._autoformat
.find('YMMD') != -1: self
._datestyle
= 'YMD'
1248 elif self
._autoformat
.find('YMMMD') != -1: self
._datestyle
= 'YMD'
1249 elif self
._autoformat
.find('DMMY') != -1: self
._datestyle
= 'DMY'
1250 elif self
._autoformat
.find('DMMMY') != -1: self
._datestyle
= 'DMY'
1253 if self
.controlInitialized
:
1254 # Then the base control is available for configuration;
1255 # take action on base control based on new settings, as appropriate.
1256 if kwargs
.has_key('useFixedWidthFont'):
1257 # Set control font - fixed width by default
1260 if reset_args
.has_key('reset_mask') or not self
._GetValue
().strip():
1261 self
._SetInitialValue
()
1264 self
.SetClientSize(self
._calcSize
())
1266 # Set value/type-specific formatting
1267 self
._applyFormatting
()
1270 def SetMaskParameters(self
, **kwargs
):
1271 """ old name for this function """
1272 return self
.SetCtrlParameters(**kwargs
)
1275 def GetCtrlParameter(self
, paramname
):
1277 Routine for retrieving the value of any given parameter
1279 if wxMaskedEditMixin
.valid_ctrl_params
.has_key(paramname
):
1280 return getattr(self
, '_' + paramname
)
1281 elif Field
.valid_params
.has_key(paramname
):
1282 return self
._ctrl
_constraints
._GetParameter
(paramname
)
1284 TypeError('%s.GetCtrlParameter: invalid parameter "%s"' % (self
.name
, paramname
))
1286 def GetMaskParameter(self
, paramname
):
1287 """ old name for this function """
1288 return self
.GetCtrlParameter(paramname
)
1290 def SetFieldParameters(self
, field_index
, **kwargs
):
1292 Routine provided to modify the parameters of a given field.
1293 Because changes to fields can affect the overall control,
1294 direct access to the fields is prevented, and the control
1295 is always "reconfigured" after setting a field parameter.
1297 if field_index
not in self
._field
_indices
:
1298 raise IndexError('%s: %s is not a valid field for this control.' % (self
.name
, str(field_index
)))
1299 # set parameters as requested:
1300 self
._fields
[field_index
]._SetParameters
(**kwargs
)
1302 # Possibly reprogram control template due to resulting changes, and ensure
1303 # control-level params are still propagated to fields:
1304 self
._configure
(self
._previous
_mask
)
1306 if self
.controlInitialized
:
1307 if kwargs
.has_key('fillChar') or kwargs
.has_key('defaultValue'):
1308 self
._SetInitialValue
()
1311 self
.SetClientSize(self
._calcSize
())
1313 # Set value/type-specific formatting
1314 self
._applyFormatting
()
1317 def GetFieldParameter(self
, field_index
, paramname
):
1319 Routine provided for getting a parameter of an individual field.
1321 if field_index
not in self
._field
_indices
:
1322 raise IndexError('%s: %s is not a valid field for this control.' % (self
.name
, str(field_index
)))
1323 elif Field
.valid_params
.has_key(paramname
):
1324 return self
._fields
[field_index
]._GetParameter
(paramname
)
1326 TypeError('%s.GetFieldParameter: invalid parameter "%s"' % (self
.name
, paramname
))
1329 def _SetKeycodeHandler(self
, keycode
, func
):
1331 This function adds and/or replaces key event handling functions
1332 used by the control. <func> should take the event as argument
1333 and return False if no further action on the key is necessary.
1335 self
._keyhandlers
[keycode
] = func
1338 def _SetKeyHandler(self
, char
, func
):
1340 This function adds and/or replaces key event handling functions
1341 for ascii characters. <func> should take the event as argument
1342 and return False if no further action on the key is necessary.
1344 self
._SetKeycodeHandler
(ord(char
), func
)
1347 def _AddNavKeycode(self
, keycode
, handler
=None):
1349 This function allows a derived subclass to augment the list of
1350 keycodes that are considered "navigational" keys.
1352 self
._nav
.append(keycode
)
1354 self
._keyhandlers
[keycode
] = handler
1357 def _AddNavKey(self
, char
, handler
=None):
1359 This function is a convenience function so you don't have to
1360 remember to call ord() for ascii chars to be used for navigation.
1362 self
._AddNavKeycode
(ord(char
), handler
)
1365 def _GetNavKeycodes(self
):
1367 This function retrieves the current list of navigational keycodes for
1373 def _SetNavKeycodes(self
, keycode_func_tuples
):
1375 This function allows you to replace the current list of keycode processed
1376 as navigation keys, and bind associated optional keyhandlers.
1379 for keycode
, func
in keycode_func_tuples
:
1380 self
._nav
.append(keycode
)
1382 self
._keyhandlers
[keycode
] = func
1385 def _processMask(self
, mask
):
1387 This subroutine expands {n} syntax in mask strings, and looks for escaped
1388 special characters and returns the expanded mask, and an dictionary
1389 of booleans indicating whether or not a given position in the mask is
1390 a mask character or not.
1392 dbg('_processMask: mask', mask
, indent
=1)
1393 # regular expression for parsing c{n} syntax:
1394 rex
= re
.compile('([' +string
.join(maskchars
,"") + '])\{(\d+)\}')
1396 match
= rex
.search(s
)
1397 while match
: # found an(other) occurrence
1398 maskchr
= s
[match
.start(1):match
.end(1)] # char to be repeated
1399 repcount
= int(s
[match
.start(2):match
.end(2)]) # the number of times
1400 replacement
= string
.join( maskchr
* repcount
, "") # the resulting substr
1401 s
= s
[:match
.start(1)] + replacement
+ s
[match
.end(2)+1:] #account for trailing '}'
1402 match
= rex
.search(s
) # look for another such entry in mask
1404 self
._decimalChar
= self
._ctrl
_constraints
._decimalChar
1405 self
._shiftDecimalChar
= self
._ctrl
_constraints
._shiftDecimalChar
1407 self
._isDec
= isDecimal(s
, self
._decimalChar
) and not self
._ctrl
_constraints
._validRegex
1408 self
._isInt
= isInteger(s
) and not self
._ctrl
_constraints
._validRegex
1409 self
._signOk
= '-' in self
._ctrl
_constraints
._formatcodes
and (self
._isDec
or self
._isInt
)
1410 dbg('isDecimal(%s, %c)?' % (s
, self
._decimalChar
), isDecimal(s
, self
._decimalChar
),
1411 'ctrl regex:', self
._ctrl
_constraints
._validRegex
)
1413 if self
._signOk
and s
[0] != ' ':
1415 if self
._ctrl
_constraints
._defaultValue
and self
._ctrl
_constraints
._defaultValue
[0] != ' ':
1416 self
._ctrl
_constraints
._defaultValue
= ' ' + self
._ctrl
_constraints
._defaultValue
1418 # Now, go build up a dictionary of booleans, indexed by position,
1419 # indicating whether or not a given position is masked or not
1423 if s
[i
] == '\\': # if escaped character:
1424 ismasked
[i
] = False # mark position as not a mask char
1425 if i
+1 < len(s
): # if another char follows...
1426 s
= s
[:i
] + s
[i
+1:] # elide the '\'
1427 if i
+2 < len(s
) and s
[i
+1] == '\\':
1428 # if next char also a '\', char is a literal '\'
1429 s
= s
[:i
] + s
[i
+1:] # elide the 2nd '\' as well
1430 else: # else if special char, mark position accordingly
1431 ismasked
[i
] = s
[i
] in maskchars
1432 ## dbg('ismasked[%d]:' % i, ismasked[i], s)
1433 i
+= 1 # increment to next char
1434 ## dbg('ismasked:', ismasked)
1439 def _calcFieldExtents(self
):
1441 Subroutine responsible for establishing/configuring field instances with
1442 indices and editable extents appropriate to the specified mask, and building
1443 the lookup table mapping each position to the corresponding field.
1447 ## Create dictionary of positions,characters in mask
1449 for charnum
in range( len( self
._mask
)):
1450 self
.maskdict
[charnum
] = self
._mask
[charnum
:charnum
+1]
1452 # For the current mask, create an ordered list of field extents
1453 # and a dictionary of positions that map to field indices:
1454 self
._lookupField
= {}
1456 if self
._signOk
: start
= 1
1460 # Skip field "discovery", and just construct a 2-field control with appropriate
1461 # constraints for a floating-point entry.
1463 # .setdefault always constructs 2nd argument even if not needed, so we do this
1464 # the old-fashioned way...
1465 if not self
._fields
.has_key(0):
1466 self
._fields
[0] = Field()
1467 if not self
._fields
.has_key(1):
1468 self
._fields
[1] = Field()
1470 self
._decimalpos
= string
.find( self
._mask
, self
._decimalChar
)
1471 dbg('decimal pos =', self
._decimalpos
)
1473 formatcodes
= self
._fields
[0]._GetParameter
('formatcodes')
1474 if 'R' not in formatcodes
: formatcodes
+= 'R'
1475 self
._fields
[0]._SetParameters
(index
=0, extent
=(start
, self
._decimalpos
),
1476 mask
=self
._mask
[start
:self
._decimalpos
], formatcodes
=formatcodes
)
1478 self
._fields
[1]._SetParameters
(index
=1, extent
=(self
._decimalpos
+1, len(self
._mask
)),
1479 mask
=self
._mask
[self
._decimalpos
+1:len(self
._mask
)])
1481 for i
in range(self
._decimalpos
+1):
1482 self
._lookupField
[i
] = 0
1484 for i
in range(self
._decimalpos
+1, len(self
._mask
)+1):
1485 self
._lookupField
[i
] = 1
1488 # Skip field "discovery", and just construct a 1-field control with appropriate
1489 # constraints for a integer entry.
1490 if not self
._fields
.has_key(0):
1491 self
._fields
[0] = Field(index
=0)
1492 self
._fields
[0]._SetParameters
(extent
=(start
, len(self
._mask
)),
1493 mask
=self
._mask
[start
:len(self
._mask
)])
1495 for i
in range(len(self
._mask
)+1):
1496 self
._lookupField
[i
] = 0
1498 # generic control; parse mask to figure out where the fields are:
1501 i
= self
._findNextEntry
(pos
,adjustInsert
=False) # go to 1st entry point:
1502 if i
< len(self
._mask
): # no editable chars!
1503 for j
in range(pos
, i
+1):
1504 self
._lookupField
[j
] = field_index
1505 pos
= i
# figure out field for 1st editable space:
1507 while i
<= len(self
._mask
):
1508 ## dbg('searching: outer field loop: i = ', i)
1509 if self
._isMaskChar
(i
):
1510 ## dbg('1st char is mask char; recording edit_start=', i)
1512 # Skip to end of editable part of current field:
1513 while i
< len(self
._mask
) and self
._isMaskChar
(i
):
1514 self
._lookupField
[i
] = field_index
1516 ## dbg('edit_end =', i)
1518 self
._lookupField
[i
] = field_index
1519 ## dbg('self._fields.has_key(%d)?' % field_index, self._fields.has_key(field_index))
1520 if not self
._fields
.has_key(field_index
):
1521 self
._fields
[field_index
] = Field()
1522 self
._fields
[field_index
]._SetParameters
(
1524 extent
=(edit_start
, edit_end
),
1525 mask
=self
._mask
[edit_start
:edit_end
])
1527 i
= self
._findNextEntry
(pos
, adjustInsert
=False) # go to next field:
1529 for j
in range(pos
, i
+1):
1530 self
._lookupField
[j
] = field_index
1531 if i
>= len(self
._mask
):
1532 break # if past end, we're done
1535 ## dbg('next field:', field_index)
1537 indices
= self
._fields
.keys()
1539 self
._field
_indices
= indices
[1:]
1540 ## dbg('lookupField map:', indent=1)
1541 ## for i in range(len(self._mask)):
1542 ## dbg('pos %d:' % i, self._lookupField[i])
1545 # Verify that all field indices specified are valid for mask:
1546 for index
in self
._fields
.keys():
1547 if index
not in [-1] + self
._lookupField
.values():
1549 raise IndexError('field %d is not a valid field for mask "%s"' % (index
, self
._mask
))
1552 def _calcTemplate(self
, reset_fillchar
, reset_default
):
1554 Subroutine for processing current fillchars and default values for
1555 whole control and individual fields, constructing the resulting
1556 overall template, and adjusting the current value as necessary.
1559 if self
._ctrl
_constraints
._defaultValue
:
1562 for field
in self
._fields
.values():
1563 if field
._defaultValue
and not reset_default
:
1565 dbg('default set?', default_set
)
1567 # Determine overall new template for control, and keep track of previous
1568 # values, so that current control value can be modified as appropriate:
1569 if self
.controlInitialized
: curvalue
= list(self
._GetValue
())
1570 else: curvalue
= None
1572 if hasattr(self
, '_fillChar'): old_fillchars
= self
._fillChar
1573 else: old_fillchars
= None
1575 if hasattr(self
, '_template'): old_template
= self
._template
1576 else: old_template
= None
1583 for field
in self
._fields
.values():
1584 field
._template
= ""
1586 for pos
in range(len(self
._mask
)):
1588 field
= self
._FindField
(pos
)
1589 ## dbg('field:', field._index)
1590 start
, end
= field
._extent
1592 if pos
== 0 and self
._signOk
:
1593 self
._template
= ' ' # always make 1st 1st position blank, regardless of fillchar
1594 elif self
._isMaskChar
(pos
):
1595 if field
._fillChar
!= self
._ctrl
_constraints
._fillChar
and not reset_fillchar
:
1596 fillChar
= field
._fillChar
1598 fillChar
= self
._ctrl
_constraints
._fillChar
1599 self
._fillChar
[pos
] = fillChar
1601 # Replace any current old fillchar with new one in current value;
1602 # if action required, set reset_value flag so we can take that action
1603 # after we're all done
1604 if self
.controlInitialized
and old_fillchars
and old_fillchars
.has_key(pos
) and curvalue
:
1605 if curvalue
[pos
] == old_fillchars
[pos
] and old_fillchars
[pos
] != fillChar
:
1607 curvalue
[pos
] = fillChar
1609 if not field
._defaultValue
and not self
._ctrl
_constraints
._defaultValue
:
1610 ## dbg('no default value')
1611 self
._template
+= fillChar
1612 field
._template
+= fillChar
1614 elif field
._defaultValue
and not reset_default
:
1615 ## dbg('len(field._defaultValue):', len(field._defaultValue))
1616 ## dbg('pos-start:', pos-start)
1617 if len(field
._defaultValue
) > pos
-start
:
1618 ## dbg('field._defaultValue[pos-start]: "%s"' % field._defaultValue[pos-start])
1619 self
._template
+= field
._defaultValue
[pos
-start
]
1620 field
._template
+= field
._defaultValue
[pos
-start
]
1622 ## dbg('field default not long enough; using fillChar')
1623 self
._template
+= fillChar
1624 field
._template
+= fillChar
1626 if len(self
._ctrl
_constraints
._defaultValue
) > pos
:
1627 ## dbg('using control default')
1628 self
._template
+= self
._ctrl
_constraints
._defaultValue
[pos
]
1629 field
._template
+= self
._ctrl
_constraints
._defaultValue
[pos
]
1631 ## dbg('ctrl default not long enough; using fillChar')
1632 self
._template
+= fillChar
1633 field
._template
+= fillChar
1634 ## dbg('field[%d]._template now "%s"' % (field._index, field._template))
1635 ## dbg('self._template now "%s"' % self._template)
1637 self
._template
+= self
._mask
[pos
]
1639 self
._fields
[-1]._template
= self
._template
# (for consistency)
1641 if curvalue
: # had an old value, put new one back together
1642 newvalue
= string
.join(curvalue
, "")
1647 self
._defaultValue
= self
._template
1648 dbg('self._defaultValue:', self
._defaultValue
)
1649 if not self
.IsEmpty(self
._defaultValue
) and not self
.IsValid(self
._defaultValue
):
1651 raise ValueError('%s: default value of "%s" is not a valid value' % (self
.name
, self
._defaultValue
))
1653 # if no fillchar change, but old value == old template, replace it:
1654 if newvalue
== old_template
:
1655 newvalue
= self
._template
1658 self
._defaultValue
= None
1661 pos
= self
._GetInsertionPoint
()
1662 sel_start
, sel_to
= self
._GetSelection
()
1663 self
._SetValue
(newvalue
)
1664 self
._SetInsertionPoint
(pos
)
1665 self
._SetSelection
(sel_start
, sel_to
)
1668 def _propagateConstraints(self
, **reset_args
):
1670 Subroutine for propagating changes to control-level constraints and
1671 formatting to the individual fields as appropriate.
1673 parent_codes
= self
._ctrl
_constraints
._formatcodes
1674 for i
in self
._field
_indices
:
1675 field
= self
._fields
[i
]
1678 field_codes
= current_codes
= field
._GetParameter
('formatcodes')
1679 for c
in parent_codes
:
1680 if c
not in field_codes
: field_codes
+= c
1681 if field_codes
!= current_codes
:
1682 inherit_args
['formatcodes'] = field_codes
1684 include_chars
= current_includes
= field
._GetParameter
('includeChars')
1685 for c
in include_chars
:
1686 if not c
in include_chars
: include_chars
+= c
1687 if include_chars
!= current_includes
:
1688 inherit_args
['includeChars'] = include_chars
1690 exclude_chars
= current_excludes
= field
._GetParameter
('excludeChars')
1691 for c
in exclude_chars
:
1692 if not c
in exclude_chars
: exclude_chars
+= c
1693 if exclude_chars
!= current_excludes
:
1694 inherit_args
['excludeChars'] = exclude_chars
1696 if reset_args
.has_key('defaultValue') and reset_args
['defaultValue']:
1697 inherit_args
['defaultValue'] = "" # (reset for field)
1699 for param
in ['fillChar', 'groupChar', 'compareNoCase', 'emptyInvalid', 'validRequired']:
1700 if reset_args
.has_key(param
) and reset_args
[param
]:
1701 inherit_args
[param
] = self
.GetCtrlParameter(param
)
1704 field
._SetParameters
(**inherit_args
)
1707 def _validateChoices(self
):
1709 Subroutine that validates that all choices for given fields are at
1710 least of the necessary length, and that they all would be valid pastes
1711 if pasted into their respective fields.
1713 for field
in self
._fields
.values():
1715 index
= field
._index
1716 ## dbg('checking for choices for field', field._index)
1717 start
, end
= field
._extent
1718 field_length
= end
- start
1719 ## dbg('start, end, length:', start, end, field_length)
1721 for choice
in field
._choices
:
1722 valid_paste
, ignore
, replace_to
= self
._validatePaste
(choice
, start
, end
)
1725 raise ValueError('%s: "%s" could not be entered into field %d' % (self
.name
, choice
, index
))
1726 elif replace_to
> end
:
1728 raise ValueError('%s: "%s" will not fit into field %d' (self
.name
, choice
, index
))
1729 ## dbg(choice, 'valid in field', index)
1732 def _configure(self
, mask
, **reset_args
):
1734 This function sets flags for automatic styling options. It is
1735 called whenever a control or field-level parameter is set/changed.
1737 This routine does the bulk of the interdependent parameter processing, determining
1738 the field extents of the mask if changed, resetting parameters as appropriate,
1739 determining the overall template value for the control, etc.
1741 reset_args is supplied if called from control's .SetCtrlParameters()
1742 routine, and indicates which if any parameters which can be
1743 overridden by individual fields have been reset by request for the
1746 dbg('wxMaskedEditMixin::_configure("%s")' % mask
, indent
=1)
1748 # Preprocess specified mask to expand {n} syntax, handle escaped
1749 # mask characters, etc and build the resulting positionally keyed
1750 # dictionary for which positions are mask vs. template characters:
1751 self
._mask
, self
.ismasked
= self
._processMask
(mask
)
1752 dbg('processed mask:', self
._mask
)
1754 # Preserve original mask specified, for subsequent reprocessing
1755 # if parameters change.
1756 self
._previous
_mask
= mask
1759 # Set extent of field -1 to width of entire control:
1760 self
._ctrl
_constraints
._SetParameters
(extent
=(0,len(self
._mask
)))
1762 # Go parse mask to determine where each field is, construct field
1763 # instances as necessary, configure them with those extents, and
1764 # build lookup table mapping each position for control to its corresponding
1766 self
._calcFieldExtents
()
1768 # Go process defaultValues and fillchars to construct the overall
1769 # template, and adjust the current value as necessary:
1770 reset_fillchar
= reset_args
.has_key('fillChar') and reset_args
['fillChar']
1771 reset_default
= reset_args
.has_key('defaultValue') and reset_args
['defaultValue']
1773 self
._calcTemplate
(reset_fillchar
, reset_default
)
1775 # Propagate control-level formatting and character constraints to each
1776 # field if they don't already have them:
1777 self
._propagateConstraints
(**reset_args
)
1781 if self
._isDec
and self
._fields
[0]._groupChar
== self
._decimalChar
:
1782 raise AttributeError('groupChar (%s) and decimalChar (%s) must be distinct.' %
1783 (self
._fields
[0]._groupChar
, self
._decimalChar
) )
1785 # Validate that all choices for given fields are at least of the
1786 # necessary length, and that they all would be valid pastes if pasted
1787 # into their respective fields:
1788 self
._validateChoices
()
1790 dbg('fields:', indent
=1)
1791 for i
in [-1] + self
._field
_indices
:
1792 dbg('field %d:' % i
, self
._fields
[i
].__dict
__)
1795 # Set up special parameters for numeric control, if appropriate:
1797 self
._signpos
= 0 # assume it starts here, but it will move around on floats
1798 self
._SetKeyHandler
('-', self
._OnChangeSign
)
1799 self
._SetKeyHandler
('+', self
._OnChangeSign
)
1800 self
._SetKeyHandler
(' ', self
._OnChangeSign
)
1802 if self
._isDec
or self
._isInt
:
1803 dbg('Registering numeric navigation and control handlers')
1805 # Replace up/down arrow default handling:
1806 # make down act like tab, up act like shift-tab:
1807 self
._SetKeycodeHandler
(WXK_DOWN
, self
._OnChangeField
)
1808 self
._SetKeycodeHandler
(WXK_UP
, self
._OnUpNumeric
) # (adds "shift" to up arrow, and calls _OnChangeField)
1810 # On ., truncate contents right of cursor to decimal point (if any)
1811 # leaves cusor after decimal point if dec, otherwise at 0.
1812 self
._SetKeyHandler
(self
._decimalChar
, self
._OnDecimalPoint
)
1813 self
._SetKeyHandler
(self
._shiftDecimalChar
, self
._OnChangeField
) # (Shift-'.' == '>' on US keyboards)
1815 # Allow selective insert of groupchar in numbers:
1816 self
._SetKeyHandler
(self
._fields
[0]._groupChar
, self
._OnGroupChar
)
1821 def _SetInitialValue(self
, value
=""):
1823 fills the control with the generated or supplied default value.
1824 It will also set/reset the font if necessary and apply
1825 formatting to the control at this time.
1827 dbg('wxMaskedEditMixin::_SetInitialValue("%s")' % value
, indent
=1)
1829 self
._SetValue
( self
._template
)
1831 # Apply validation as appropriate to passed value
1832 self
.SetValue(value
)
1834 # Set value/type-specific formatting
1835 self
._applyFormatting
()
1839 def _calcSize(self
, size
=None):
1840 """ Calculate automatic size if allowed; must be called after the base control is instantiated"""
1841 ## dbg('wxMaskedEditMixin::_calcSize', indent=1)
1842 cont
= (size
is None or size
== wxDefaultSize
)
1844 if cont
and self
._autofit
:
1845 sizing_text
= 'M' * len(self
._mask
)
1846 if wxPlatform
!= "__WXMSW__": # give it a little extra space
1848 if wxPlatform
== "__WXMAC__": # give it even a little more...
1850 ## dbg('len(sizing_text):', len(sizing_text), 'sizing_text: "%s"' % sizing_text)
1851 w
, h
= self
.GetTextExtent(sizing_text
)
1852 size
= (w
+4, self
.GetClientSize().height
)
1853 ## dbg('size:', size, indent=0)
1858 """ Set the control's font typeface -- pass the font name as str."""
1859 ## dbg('wxMaskedEditMixin::_setFont', indent=1)
1860 if not self
._useFixedWidthFont
:
1861 self
._font
= wxSystemSettings_GetFont(wxSYS_DEFAULT_GUI_FONT
)
1863 font
= self
.GetFont() # get size, weight, etc from current font
1865 # Set to teletype font (guaranteed to be mappable to all wxWindows
1867 self
._font
= wxFont( font
.GetPointSize(), wxTELETYPE
, font
.GetStyle(),
1868 font
.GetWeight(), font
.GetUnderlined())
1869 ## dbg('font string: "%s"' % font.GetNativeFontInfo().ToString())
1871 self
.SetFont(self
._font
)
1875 def _OnTextChange(self
, event
):
1877 Handler for EVT_TEXT event.
1878 self._Change() is provided for subclasses, and may return False to
1879 skip this method logic. This function returns True if the event
1880 detected was a legitimate event, or False if it was a "bogus"
1881 EVT_TEXT event. (NOTE: There is currently an issue with calling
1882 .SetValue from within the EVT_CHAR handler that causes duplicate
1883 EVT_TEXT events for the same change.)
1885 newvalue
= self
._GetValue
()
1886 dbg('wxMaskedEditMixin::_OnTextChange: value: "%s"' % newvalue
, indent
=1)
1888 if self
._ignoreChange
: # ie. if an "intermediate text change event"
1892 ##! WS: For some inexplicable reason, every wxTextCtrl.SetValue
1893 ## call is generating two (2) EVT_TEXT events.
1894 ## This is the only mechanism I can find to mask this problem:
1895 if newvalue
== self
._oldvalue
:
1896 dbg('ignoring bogus text change event', indent
=0)
1898 dbg('oldvalue: "%s", newvalue: "%s"' % (self
._oldvalue
, newvalue
))
1900 if self
._signOk
and self
._isNeg
and newvalue
.find('-') == -1:
1902 text
, self
._signpos
= self
._getSignedValue
()
1903 self
._CheckValid
() # Recolor control as appropriate
1906 self
._oldvalue
= newvalue
# Save last seen value for next iteration
1911 def _OnKeyDown(self
, event
):
1913 This function allows the control to capture Ctrl-events like Ctrl-tab,
1914 that are not normally seen by the "cooked" EVT_CHAR routine.
1916 # Get keypress value, adjusted by control options (e.g. convert to upper etc)
1917 key
= event
.GetKeyCode()
1918 if key
in self
._nav
and event
.ControlDown():
1919 # then this is the only place we will likely see these events;
1921 dbg('wxMaskedEditMixin::OnKeyDown: calling _OnChar')
1924 # else allow regular EVT_CHAR key processing
1928 def _OnChar(self
, event
):
1930 This is the engine of wxMaskedEdit controls. It examines each keystroke,
1931 decides if it's allowed, where it should go or what action to take.
1933 dbg('wxMaskedEditMixin::_OnChar', indent
=1)
1935 # Get keypress value, adjusted by control options (e.g. convert to upper etc)
1936 key
= event
.GetKeyCode()
1937 orig_pos
= self
._GetInsertionPoint
()
1938 orig_value
= self
._GetValue
()
1939 dbg('keycode = ', key
)
1940 dbg('current pos = ', orig_pos
)
1941 dbg('current selection = ', self
._GetSelection
())
1943 if not self
._Keypress
(key
):
1947 # If no format string for this control, or the control is marked as "read-only",
1948 # skip the rest of the special processing, and just "do the standard thing:"
1949 if not self
._mask
or not self
._IsEditable
():
1954 # Process navigation and control keys first, with
1955 # position/selection unadulterated:
1956 if key
in self
._nav
+ self
._control
:
1957 if self
._keyhandlers
.has_key(key
):
1958 keep_processing
= self
._keyhandlers
[key
](event
)
1959 if self
._GetValue
() != orig_value
:
1960 self
.modified
= True
1961 if not keep_processing
:
1964 self
._applyFormatting
()
1968 # Else... adjust the position as necessary for next input key,
1969 # and determine resulting selection:
1970 pos
= self
._adjustPos
( orig_pos
, key
) ## get insertion position, adjusted as needed
1971 sel_start
, sel_to
= self
._GetSelection
() ## check for a range of selected text
1972 dbg("pos, sel_start, sel_to:", pos
, sel_start
, sel_to
)
1974 keep_processing
= True
1975 # Capture user past end of format field
1976 if pos
> len(self
.maskdict
):
1977 dbg("field length exceeded:",pos
)
1978 keep_processing
= False
1981 if self
._isMaskChar
(pos
): ## Get string of allowed characters for validation
1982 okchars
= self
._getAllowedChars
(pos
)
1984 dbg('Not a valid position: pos = ', pos
,"chars=",maskchars
)
1987 key
= self
._adjustKey
(pos
, key
) # apply formatting constraints to key:
1989 if self
._keyhandlers
.has_key(key
):
1990 # there's an override for default behavior; use override function instead
1991 dbg('using supplied key handler:', self
._keyhandlers
[key
])
1992 keep_processing
= self
._keyhandlers
[key
](event
)
1993 if self
._GetValue
() != orig_value
:
1994 self
.modified
= True
1995 if not keep_processing
:
1998 # else skip default processing, but do final formatting
1999 if key
< WXK_SPACE
or key
> 255:
2000 dbg('key < WXK_SPACE or key > 255')
2001 event
.Skip() # non alphanumeric
2002 keep_processing
= False
2004 field
= self
._FindField
(pos
)
2005 dbg("key ='%s'" % chr(key
))
2007 dbg('okSpaces?', field
._okSpaces
)
2009 if chr(key
) in field
._excludeChars
+ self
._ctrl
_constraints
._excludeChars
:
2010 keep_processing
= False
2011 if (not wxValidator_IsSilent()) and orig_pos
== pos
:
2014 if keep_processing
and self
._isCharAllowed
( chr(key
), pos
, checkRegex
= True ):
2015 dbg("key allowed by mask")
2016 # insert key into candidate new value, but don't change control yet:
2017 oldstr
= self
._GetValue
()
2018 newstr
, newpos
= self
._insertKey
(chr(key
), pos
, sel_start
, sel_to
, self
._GetValue
())
2019 dbg("str with '%s' inserted:" % chr(key
), '"%s"' % newstr
)
2020 if self
._ctrl
_constraints
._validRequired
and not self
.IsValid(newstr
):
2021 dbg('not valid; checking to see if adjusted string is:')
2022 keep_processing
= False
2023 if self
._isDec
and newstr
!= self
._template
:
2024 newstr
= self
._adjustDec
(newstr
)
2025 dbg('adjusted str:', newstr
)
2026 if self
.IsValid(newstr
):
2028 keep_processing
= True
2029 wxCallAfter(self
._SetInsertionPoint
, self
._decimalpos
)
2030 if not keep_processing
:
2031 dbg("key disallowed by validation")
2032 if not wxValidator_IsSilent() and orig_pos
== pos
:
2038 # special case: adjust date value as necessary:
2039 if self
._isDate
and newstr
!= self
._template
:
2040 newstr
= self
._adjustDate
(newstr
)
2041 dbg('adjusted newstr:', newstr
)
2043 if newstr
!= orig_value
:
2044 self
.modified
= True
2046 wxCallAfter(self
._SetValue
, newstr
)
2048 # Adjust insertion point on date if just entered 2 digit year, and there are now 4 digits:
2049 if not self
.IsDefault() and self
._isDate
and self
._4digityear
:
2050 year2dig
= self
._dateExtent
- 2
2051 if pos
== year2dig
and unadjusted
[year2dig
] != newstr
[year2dig
]:
2054 wxCallAfter(self
._SetInsertionPoint
, newpos
)
2055 newfield
= self
._FindField
(newpos
)
2056 if newfield
!= field
and newfield
._selectOnFieldEntry
:
2057 wxCallAfter(self
._SetSelection
, newfield
._extent
[0], newfield
._extent
[1])
2058 keep_processing
= false
2060 dbg('char not allowed; orig_pos == pos?', orig_pos
== pos
)
2061 keep_processing
= False
2062 if (not wxValidator_IsSilent()) and orig_pos
== pos
:
2065 self
._applyFormatting
()
2067 # Move to next insertion point
2068 if keep_processing
and key
not in self
._nav
:
2069 pos
= self
._GetInsertionPoint
()
2070 next_entry
= self
._findNextEntry
( pos
)
2071 if pos
!= next_entry
:
2072 dbg("moving from %(pos)d to next valid entry: %(next_entry)d" % locals())
2073 wxCallAfter(self
._SetInsertionPoint
, next_entry
)
2075 if self
._isTemplateChar
(pos
):
2076 self
._AdjustField
(pos
)
2080 def _FindFieldExtent(self
, pos
=None, getslice
=False, value
=None):
2081 """ returns editable extent of field corresponding to
2082 position pos, and, optionally, the contents of that field
2083 in the control or the value specified.
2084 Template chars are bound to the preceding field.
2085 For masks beginning with template chars, these chars are ignored
2086 when calculating the current field.
2088 Eg: with template (###) ###-####,
2089 >>> self._FindFieldExtent(pos=0)
2091 >>> self._FindFieldExtent(pos=1)
2093 >>> self._FindFieldExtent(pos=5)
2095 >>> self._FindFieldExtent(pos=6)
2097 >>> self._FindFieldExtent(pos=10)
2101 dbg('wxMaskedEditMixin::_FindFieldExtent(pos=%s, getslice=%s)' % (
2102 str(pos
), str(getslice
)) ,indent
=1)
2104 field
= self
._FindField
(pos
)
2107 return None, None, ""
2110 edit_start
, edit_end
= field
._extent
2112 if value
is None: value
= self
._GetValue
()
2113 slice = value
[edit_start
:edit_end
]
2114 dbg('edit_start:', edit_start
, 'edit_end:', edit_end
, 'slice: "%s"' % slice)
2116 return edit_start
, edit_end
, slice
2118 dbg('edit_start:', edit_start
, 'edit_end:', edit_end
)
2120 return edit_start
, edit_end
2123 def _FindField(self
, pos
=None):
2125 Returns the field instance in which pos resides.
2126 Template chars are bound to the preceding field.
2127 For masks beginning with template chars, these chars are ignored
2128 when calculating the current field.
2131 ## dbg('wxMaskedEditMixin::_FindField(pos=%s)' % str(pos) ,indent=1)
2132 if pos
is None: pos
= self
._GetInsertionPoint
()
2133 elif pos
< 0 or pos
> len(self
._mask
):
2134 raise IndexError('position %s out of range of control' % str(pos
))
2136 if len(self
._fields
) == 0:
2142 return self
._fields
[self
._lookupField
[pos
]]
2145 def ClearValue(self
):
2146 """ Blanks the current control value by replacing it with the default value."""
2147 dbg("wxMaskedEditMixin::ClearValue - value reset to default value (template)")
2148 self
._SetValue
( self
._template
)
2149 self
._SetInsertionPoint
(0)
2153 def _baseCtrlEventHandler(self
, event
):
2155 This function is used whenever a key should be handled by the base control.
2161 def _OnUpNumeric(self
, event
):
2163 Makes up-arrow act like shift-tab should; ie. take you to start of
2166 dbg('wxMaskedEditMixin::_OnUpNumeric', indent
=1)
2167 event
.m_shiftDown
= 1
2168 dbg('event.ShiftDown()?', event
.ShiftDown())
2169 self
._OnChangeField
(event
)
2173 def _OnArrow(self
, event
):
2175 Used in response to left/right navigation keys; makes these actions skip
2176 over mask template chars.
2178 dbg("wxMaskedEditMixin::_OnArrow", indent
=1)
2179 pos
= self
._GetInsertionPoint
()
2180 keycode
= event
.GetKeyCode()
2181 sel_start
, sel_to
= self
._GetSelection
()
2182 entry_end
= self
._goEnd
(getPosOnly
=True)
2183 if keycode
in (WXK_RIGHT
, WXK_DOWN
):
2184 if( ( not self
._isTemplateChar
(pos
) and pos
+1 > entry_end
)
2185 or ( self
._isTemplateChar
(pos
) and pos
>= entry_end
) ):
2188 elif self
._isTemplateChar
(pos
):
2189 self
._AdjustField
(pos
)
2190 elif keycode
in (WXK_LEFT
,WXK_UP
) and pos
> 0 and self
._isTemplateChar
(pos
-1):
2191 dbg('adjusting field')
2192 self
._AdjustField
(pos
)
2194 # treat as shifted up/down arrows as tab/reverse tab:
2195 if event
.ShiftDown() and keycode
in (WXK_UP
, WXK_DOWN
):
2196 # remove "shifting" and treat as (forward) tab:
2197 event
.m_shiftDown
= False
2198 keep_processing
= self
._OnChangeField
(event
)
2200 elif self
._FindField
(pos
)._selectOnFieldEntry
:
2201 if( keycode
in (WXK_UP
, WXK_LEFT
)
2203 and self
._isTemplateChar
(sel_start
-1)):
2205 # call _OnChangeField to handle "ctrl-shifted event"
2206 # (which moves to previous field and selects it.)
2207 event
.m_shiftDown
= True
2208 event
.m_ControlDown
= True
2209 keep_processing
= self
._OnChangeField
(event
)
2210 elif( keycode
in (WXK_DOWN
, WXK_RIGHT
)
2211 and sel_to
!= len(self
._mask
)
2212 and self
._isTemplateChar
(sel_to
)):
2214 # when changing field to the right, ensure don't accidentally go left instead
2215 event
.m_shiftDown
= False
2216 keep_processing
= self
._OnChangeField
(event
)
2218 # treat arrows as normal, allowing selection
2222 if( (sel_to
== self
._fields
[0]._extent
[0] and keycode
== WXK_LEFT
)
2223 or (sel_to
== len(self
._mask
) and keycode
== WXK_RIGHT
) ):
2224 if not wxValidator_IsSilent():
2227 # treat arrows as normal, allowing selection
2229 dbg('skipping event')
2232 keep_processing
= False
2234 return keep_processing
2237 def _OnCtrl_S(self
, event
):
2238 """ Default Ctrl-S handler; prints value information if demo enabled. """
2239 dbg("wxMaskedEditMixin::_OnCtrl_S")
2241 print 'wxMaskedEditMixin.GetValue() = "%s"\nwxMaskedEditMixin.GetPlainValue() = "%s"' % (self
.GetValue(), self
.GetPlainValue())
2242 print "Valid? => " + str(self
.IsValid())
2243 print "Current field, start, end, value =", str( self
._FindFieldExtent
(getslice
=True))
2247 def _OnCtrl_X(self
, event
):
2248 """ Handles ctrl-x keypress in control. Should return False to skip other processing. """
2249 dbg("wxMaskedEditMixin::_OnCtrl_X", indent
=1)
2255 def _OnCtrl_V(self
, event
):
2256 """ Handles ctrl-V keypress in control. Should return False to skip other processing. """
2257 dbg("wxMaskedEditMixin::_OnCtrl_V", indent
=1)
2263 def _OnCtrl_A(self
,event
):
2264 """ Handles ctrl-a keypress in control. Should return False to skip other processing. """
2265 end
= self
._goEnd
(getPosOnly
=True)
2266 if event
.ShiftDown():
2267 wxCallAfter(self
._SetInsertionPoint
, 0)
2268 wxCallAfter(self
._SetSelection
, 0, len(self
._mask
))
2270 wxCallAfter(self
._SetInsertionPoint
, 0)
2271 wxCallAfter(self
._SetSelection
, 0, end
)
2275 def _OnErase(self
,event
):
2276 """ Handles backspace and delete keypress in control. Should return False to skip other processing."""
2277 dbg("wxMaskedEditMixin::_OnErase", indent
=1)
2278 sel_start
, sel_to
= self
._GetSelection
() ## check for a range of selected text
2279 key
= event
.GetKeyCode()
2280 if( ((sel_to
== 0 or sel_to
== self
._fields
[0]._extent
[0]) and key
== WXK_BACK
)
2281 or (sel_to
== len(self
._mask
) and key
== WXK_DELETE
)):
2282 if not wxValidator_IsSilent():
2287 field
= self
._FindField
(sel_to
)
2288 start
, end
= field
._extent
2289 value
= self
._GetValue
()
2291 if( field
._insertRight
2293 and sel_start
>= start
and sel_to
== end
# within field
2294 and value
[start
:end
] != self
._template
[start
:end
]): # and field not empty
2296 dbg('sel_start, start:', sel_start
, start
)
2297 # special case: backspace at the end of a right insert field shifts contents right to cursor
2299 if sel_start
== end
: # select "last char in field"
2302 newfield
= value
[start
:sel_start
]
2303 dbg('cut newfield: "%s"' % newfield
)
2305 for i
in range(start
, end
- len(newfield
)):
2308 elif self
._signOk
and self
._isNeg
and newfield
.find('-') == -1 and i
== 1:
2311 left
+= self
._template
[i
] # this can produce strange results in combination with default values...
2312 newfield
= left
+ newfield
2313 dbg('filled newfield: "%s"' % newfield
)
2314 newstr
= value
[:start
] + newfield
+ value
[end
:]
2315 if self
._signOk
and self
._isNeg
and newstr
[0] == '-':
2316 newstr
= ' ' + newstr
[1:]
2319 if sel_start
== sel_to
:
2320 dbg("current sel_start, sel_to:", sel_start
, sel_to
)
2322 sel_start
, sel_to
= sel_to
-1, sel_to
-1
2323 dbg("new sel_start, sel_to:", sel_start
, sel_to
)
2324 if field
._padZero
and not value
[start
:sel_to
].replace('0', '').replace(' ','').replace(field
._fillChar
, ''):
2325 # preceding chars (if any) are zeros, blanks or fillchar; new char should be 0:
2328 newchar
= self
._template
[sel_to
] ## get an original template character to "clear" the current char
2329 dbg('value = "%s"' % value
, 'value[%d] = "%s"' %(sel_start
, value
[sel_start
]))
2331 if self
._isTemplateChar
(sel_to
):
2335 newstr
, newpos
= self
._insertKey
(newchar
, sel_to
, sel_start
, sel_to
, value
)
2340 for i
in range(sel_start
, sel_to
):
2342 newchar
= self
._template
[pos
] ## get an original template character to "clear" the current char
2344 if not self
._isTemplateChar
(pos
):
2345 newstr
, newpos
= self
._insertKey
(newchar
, pos
, sel_start
, sel_to
, newstr
)
2347 pos
= sel_start
# put cursor back at beginning of selection
2348 dbg('newstr:', newstr
)
2350 # if erasure results in an invalid field, disallow it:
2351 dbg('field._validRequired?', field
._validRequired
)
2352 dbg('field.IsValid("%s")?' % newstr
[start
:end
], field
.IsValid(newstr
[start
:end
]))
2353 if field
._validRequired
and not field
.IsValid(newstr
[start
:end
]):
2354 if not wxValidator_IsSilent():
2359 # if erasure results in an invalid value, disallow it:
2360 if self
._ctrl
_constraints
._validRequired
and not self
.IsValid(newstr
):
2361 if not wxValidator_IsSilent():
2366 dbg('setting value (later) to', newstr
)
2367 wxCallAfter(self
._SetValue
, newstr
)
2368 dbg('setting insertion point (later) to', pos
)
2369 wxCallAfter(self
._SetInsertionPoint
, pos
)
2374 def _OnEnd(self
,event
):
2375 """ Handles End keypress in control. Should return False to skip other processing. """
2376 dbg("wxMaskedEditMixin::_OnEnd", indent
=1)
2377 pos
= self
._adjustPos
(self
._GetInsertionPoint
(), event
.GetKeyCode())
2378 end
= self
._goEnd
(getPosOnly
=True)
2380 if event
.ShiftDown():
2381 dbg("shift-end; select to end of non-whitespace")
2382 wxCallAfter(self
._SetInsertionPoint
, pos
)
2383 wxCallAfter(self
._SetSelection
, pos
, end
)
2384 elif event
.ControlDown():
2385 dbg("control-end; select to end of control")
2386 wxCallAfter(self
._SetInsertionPoint
, pos
)
2387 wxCallAfter(self
._SetSelection
, pos
, len(self
._mask
))
2389 onChar
= self
._goEnd
()
2394 def _OnReturn(self
, event
):
2396 Changes the event to look like a tab event, so we can then call
2397 event.Skip() on it, and have the parent form "do the right thing."
2399 event
.m_keyCode
= WXK_TAB
2403 def _OnHome(self
,event
):
2404 """ Handles Home keypress in control. Should return False to skip other processing."""
2405 dbg("wxMaskedEditMixin::_OnHome", indent
=1)
2406 pos
= self
._adjustPos
(self
._GetInsertionPoint
(), event
.GetKeyCode())
2407 sel_start
, sel_to
= self
._GetSelection
()
2409 if event
.ShiftDown():
2410 dbg("shift-home; select to beginning of non-whitespace")
2413 wxCallAfter(self
._SetInsertionPoint
, pos
)
2414 wxCallAfter(self
._SetSelection
, 0, pos
)
2421 def _OnChangeField(self
, event
):
2423 Primarily handles TAB events, but can be used for any key that
2424 designer wants to change fields within a masked edit control.
2425 NOTE: at the moment, although coded to handle shift-TAB and
2426 control-shift-TAB, these events are not sent to the controls
2429 dbg('wxMaskedEditMixin::_OnChangeField', indent
= 1)
2430 # determine end of current field:
2431 pos
= self
._GetInsertionPoint
()
2432 dbg('current pos:', pos
)
2435 masklength
= len(self
._mask
)
2436 if masklength
< 0: # no fields; process tab normally
2437 self
._AdjustField
(pos
)
2438 if event
.GetKeyCode() == WXK_TAB
:
2439 dbg('tab to next ctrl')
2445 field
= self
._FindField
(pos
)
2447 if event
.ShiftDown():
2450 # NOTE: doesn't yet work with SHIFT-tab under wx; the control
2451 # never sees this event! (But I've coded for it should it ever work,
2452 # and it *does* work for '.' in wxIpAddrCtrl.)
2454 index
= field
._index
2455 begin_field
= field
._extent
[0]
2456 if event
.ControlDown():
2457 dbg('select to beginning of field:', begin_field
, pos
)
2458 wxCallAfter(self
._SetInsertionPoint
, begin_field
)
2459 wxCallAfter(self
._SetSelection
, begin_field
, pos
)
2464 # We're already in the 1st field; process shift-tab normally:
2465 self
._AdjustField
(pos
)
2466 if event
.GetKeyCode() == WXK_TAB
:
2467 dbg('tab to previous ctrl')
2470 dbg('position at beginning')
2471 wxCallAfter(self
._SetInsertionPoint
, begin_field
)
2475 # find beginning of previous field:
2476 begin_prev
= self
._FindField
(begin_field
-1)._extent
[0]
2477 self
._AdjustField
(pos
)
2478 dbg('repositioning to', begin_prev
)
2479 wxCallAfter(self
._SetInsertionPoint
, begin_prev
)
2480 if self
._FindField
(begin_prev
)._selectOnFieldEntry
:
2481 edit_start
, edit_end
= self
._FindFieldExtent
(begin_prev
)
2482 wxCallAfter(self
._SetSelection
, edit_start
, edit_end
)
2489 end_field
= field
._extent
[1]
2490 if event
.ControlDown():
2491 dbg('select to end of field:', pos
, end_field
)
2492 wxCallAfter(self
._SetInsertionPoint
, pos
)
2493 wxCallAfter(self
._SetSelection
, pos
, end_field
)
2497 dbg('end of current field:', end_field
)
2498 dbg('go to next field')
2499 if end_field
== self
._fields
[self
._field
_indices
[-1]]._extent
[1]:
2500 self
._AdjustField
(pos
)
2501 if event
.GetKeyCode() == WXK_TAB
:
2502 dbg('tab to next ctrl')
2505 dbg('position at end')
2506 wxCallAfter(self
._SetInsertionPoint
, end_field
)
2510 # we have to find the start of the next field
2511 next_pos
= self
._findNextEntry
(end_field
)
2512 if next_pos
== end_field
:
2513 dbg('already in last field')
2514 self
._AdjustField
(pos
)
2515 if event
.GetKeyCode() == WXK_TAB
:
2516 dbg('tab to next ctrl')
2522 self
._AdjustField
( pos
)
2524 # move cursor to appropriate point in the next field and select as necessary:
2525 field
= self
._FindField
(next_pos
)
2526 edit_start
, edit_end
= field
._extent
2527 if field
._selectOnFieldEntry
:
2528 dbg('move to ', next_pos
)
2529 wxCallAfter(self
._SetInsertionPoint
, next_pos
)
2530 edit_start
, edit_end
= self
._FindFieldExtent
(next_pos
)
2531 dbg('select', edit_start
, edit_end
)
2532 wxCallAfter(self
._SetSelection
, edit_start
, edit_end
)
2534 if field
._insertRight
:
2535 next_pos
= field
._extent
[1]
2536 dbg('move to ', next_pos
)
2537 wxCallAfter(self
._SetInsertionPoint
, next_pos
)
2542 def _OnDecimalPoint(self
, event
):
2543 dbg('wxMaskedEditMixin::_OnDecimalPoint', indent
=1)
2545 pos
= self
._adjustPos
(self
._GetInsertionPoint
(), event
.GetKeyCode())
2547 if self
._isDec
: ## handle decimal value, move to decimal place
2548 dbg('key == Decimal tab; decimal pos:', self
._decimalpos
)
2549 value
= self
._GetValue
()
2550 if pos
< self
._decimalpos
:
2551 clipped_text
= value
[0:pos
] + self
._decimalChar
+ value
[self
._decimalpos
+1:]
2552 dbg('value: "%s"' % self
._GetValue
(), 'clipped_text:', clipped_text
)
2553 newstr
= self
._adjustDec
(clipped_text
)
2555 newstr
= self
._adjustDec
(value
)
2556 wxCallAfter(self
._SetValue
, newstr
)
2557 wxCallAfter(self
._SetInsertionPoint
, self
._decimalpos
+1)
2558 keep_processing
= False
2560 if self
._isInt
: ## handle integer value, truncate from current position
2561 dbg('key == Integer decimal event')
2562 value
= self
._GetValue
()
2563 clipped_text
= value
[0:pos
]
2564 dbg('value: "%s"' % self
._GetValue
(), 'clipped_text:', clipped_text
)
2565 newstr
= self
._adjustInt
(clipped_text
)
2566 dbg('newstr: "%s"' % newstr
)
2567 wxCallAfter(self
._SetValue
, newstr
)
2568 wxCallAfter(self
._SetInsertionPoint
, len(newstr
.rstrip()))
2569 keep_processing
= False
2573 def _OnChangeSign(self
, event
):
2574 dbg('wxMaskedEditMixin::_OnChangeSign', indent
=1)
2575 key
= event
.GetKeyCode()
2576 pos
= self
._adjustPos
(self
._GetInsertionPoint
(), key
)
2577 if chr(key
) in ("-","+") or (chr(key
) == " " and pos
== self
._signpos
):
2578 cursign
= self
._isNeg
2579 dbg('cursign:', cursign
)
2581 self
._isNeg
= (not self
._isNeg
) ## flip value
2584 dbg('isNeg?', self
._isNeg
)
2586 text
, signpos
= self
._getSignedValue
()
2587 if text
is not None:
2588 self
._signpos
= signpos
2589 dbg('self._signpos now:', self
._signpos
)
2591 text
= self
._GetValue
()
2592 field
= self
._fields
[0]
2593 if field
._alignRight
and field
._fillChar
== ' ':
2594 self
._signpos
= text
.find('-')
2595 if self
._signpos
== -1:
2596 if len(text
.lstrip()) < len(text
):
2597 self
._signpos
= len(text
) - len(text
.lstrip()) - 1
2602 dbg('self._signpos now:', self
._signpos
)
2604 text
= text
[:self
._signpos
] + '-' + text
[self
._signpos
+1:]
2606 text
= text
[:self
._signpos
] + ' ' + text
[self
._signpos
+1:]
2608 wxCallAfter(self
._SetValue
, text
)
2609 wxCallAfter(self
._applyFormatting
)
2610 if pos
== self
._signpos
:
2611 wxCallAfter(self
._SetInsertionPoint
, self
._signpos
+1)
2613 wxCallAfter(self
._SetInsertionPoint
, pos
)
2615 keep_processing
= False
2617 keep_processing
= True
2619 return keep_processing
2622 def _OnGroupChar(self
, event
):
2624 This handler is only registered if the mask is a numeric mask.
2625 It allows the insertion of ',' or '.' if appropriate.
2627 dbg('wxMaskedEditMixin::_OnGroupChar', indent
=1)
2628 keep_processing
= True
2629 pos
= self
._adjustPos
(self
._GetInsertionPoint
(), event
.GetKeyCode())
2630 sel_start
, sel_to
= self
._GetSelection
()
2631 groupchar
= self
._fields
[0]._groupChar
2632 if not self
._isCharAllowed
(groupchar
, pos
, checkRegex
=True):
2633 keep_processing
= False
2634 if not wxValidator_IsSilent():
2638 newstr
, newpos
= self
._insertKey
(groupchar
, pos
, sel_start
, sel_to
, self
._GetValue
() )
2639 dbg("str with '%s' inserted:" % groupchar
, '"%s"' % newstr
)
2640 if self
._ctrl
_constraints
._validRequired
and not self
.IsValid(newstr
):
2641 keep_processing
= False
2642 if not wxValidator_IsSilent():
2646 wxCallAfter(self
._SetValue
, newstr
)
2647 wxCallAfter(self
._SetInsertionPoint
, newpos
)
2648 keep_processing
= False
2650 return keep_processing
2653 def _findNextEntry(self
,pos
, adjustInsert
=True):
2654 """ Find the insertion point for the next valid entry character position."""
2655 if self
._isTemplateChar
(pos
): # if changing fields, pay attn to flag
2656 adjustInsert
= adjustInsert
2657 else: # else within a field; flag not relevant
2658 adjustInsert
= False
2660 while self
._isTemplateChar
(pos
) and pos
< len(self
._mask
):
2663 # if changing fields, and we've been told to adjust insert point,
2664 # look at new field; if empty and right-insert field,
2665 # adjust to right edge:
2666 if adjustInsert
and pos
< len(self
._mask
):
2667 field
= self
._FindField
(pos
)
2668 start
, end
= field
._extent
2669 slice = self
._GetValue
()[start
:end
]
2670 if field
._insertRight
and field
.IsEmpty(slice):
2675 def _findNextTemplateChar(self
, pos
):
2676 """ Find the position of the next non-editable character in the mask."""
2677 while not self
._isTemplateChar
(pos
) and pos
< len(self
._mask
):
2682 def _OnAutoCompleteField(self
, event
):
2683 dbg('wxMaskedEditMixin::_OnAutoCompleteField', indent
=1)
2684 pos
= self
._GetInsertionPoint
()
2685 field
= self
._FindField
(pos
)
2686 edit_start
, edit_end
, slice = self
._FindFieldExtent
(pos
, getslice
=True)
2688 keycode
= event
.GetKeyCode()
2689 text
= slice.replace(field
._fillChar
, '')
2691 keep_processing
= True # (assume True to start)
2692 dbg('field._hasList?', field
._hasList
)
2694 dbg('choices:', field
._choices
)
2695 choices
, choice_required
= field
._choices
, field
._choiceRequired
2696 if keycode
in (WXK_PRIOR
, WXK_UP
):
2700 match_index
= self
._autoComplete
(direction
, choices
, text
, compareNoCase
=field
._compareNoCase
)
2701 if( match_index
is None
2702 and (keycode
in self
._autoCompleteKeycodes
+ [WXK_PRIOR
, WXK_NEXT
]
2703 or (keycode
in [WXK_UP
, WXK_DOWN
] and event
.ShiftDown() ) ) ):
2704 # Select the 1st thing from the list:
2706 if( match_index
is not None
2707 and ( keycode
in self
._autoCompleteKeycodes
+ [WXK_PRIOR
, WXK_NEXT
]
2708 or (keycode
in [WXK_UP
, WXK_DOWN
] and event
.ShiftDown())
2709 or (keycode
== WXK_DOWN
and len(text
) < len(choices
[match_index
])) ) ):
2711 # We're allowed to auto-complete:
2712 value
= self
._GetValue
()
2713 newvalue
= value
[:edit_start
] + choices
[match_index
] + value
[edit_end
:]
2714 dbg('match found; setting value to "%s"' % newvalue
)
2715 self
._SetValue
(newvalue
)
2716 self
._SetInsertionPoint
(edit_end
)
2717 self
._CheckValid
() # recolor as appopriate
2719 if keycode
in (WXK_UP
, WXK_DOWN
, WXK_LEFT
, WXK_RIGHT
):
2720 # treat as left right arrow if unshifted, tab/shift tab if shifted.
2721 if event
.ShiftDown():
2722 if keycode
in (WXK_DOWN
, WXK_RIGHT
):
2723 # remove "shifting" and treat as (forward) tab:
2724 event
.m_shiftDown
= False
2725 keep_processing
= self
._OnChangeField
(event
)
2727 keep_processing
= self
._OnArrow
(event
)
2728 # some other key; keep processing the key
2730 dbg('keep processing?', keep_processing
, indent
=0)
2731 return keep_processing
2734 def _autoComplete(self
, direction
, choices
, value
, compareNoCase
):
2736 This function gets called in response to Auto-complete events.
2737 It attempts to find a match to the specified value against the
2738 list of choices; if exact match, the index of then next
2739 appropriate value in the list, based on the given direction.
2740 If not an exact match, it will return the index of the 1st value from
2741 the choice list for which the partial value can be extended to match.
2742 If no match found, it will return None.
2745 dbg('nothing to match against', indent
=0)
2749 choices
= [choice
.strip().lower() for choice
in choices
]
2750 value
= value
.lower()
2752 choices
= [choice
.strip() for choice
in choices
]
2754 if value
in choices
:
2755 index
= choices
.index(value
)
2756 dbg('matched "%s"' % choices
[index
])
2758 if index
== 0: index
= len(choices
) - 1
2761 if index
== len(choices
) - 1: index
= 0
2763 dbg('change value to ', index
)
2766 value
= value
.strip()
2767 dbg('no match; try to auto-complete:')
2769 dbg('searching for "%s"' % value
)
2770 for index
in range(len(choices
)):
2771 choice
= choices
[index
]
2772 if choice
.find(value
, 0) == 0:
2773 dbg('match found:', choice
)
2776 if match
is not None:
2777 dbg('matched', match
)
2779 dbg('no match found')
2784 def _AdjustField(self
, pos
):
2786 This function gets called by default whenever the cursor leaves a field.
2787 The pos argument given is the char position before leaving that field.
2788 By default, floating point, integer and date values are adjusted to be
2789 legal in this function. Derived classes may override this function
2790 to modify the value of the control in a different way when changing fields.
2792 NOTE: these change the value immediately, and restore the cursor to
2793 the passed location, so that any subsequent code can then move it
2794 based on the operation being performed.
2796 newvalue
= value
= self
._GetValue
()
2797 field
= self
._FindField
(pos
)
2798 start
, end
, slice = self
._FindFieldExtent
(getslice
=True)
2799 newfield
= field
._AdjustField
(slice)
2800 newvalue
= value
[:start
] + newfield
+ value
[end
:]
2802 if self
._isDec
and newvalue
!= self
._template
:
2803 newvalue
= self
._adjustDec
(newvalue
)
2805 if self
._ctrl
_constraints
._isInt
and value
!= self
._template
:
2806 newvalue
= self
._adjustInt
(value
)
2808 if self
._isDate
and value
!= self
._template
:
2809 newvalue
= self
._adjustDate
(value
, fixcentury
=True)
2810 if self
._4digityear
:
2811 year2dig
= self
._dateExtent
- 2
2812 if pos
== year2dig
and value
[year2dig
] != newvalue
[year2dig
]:
2815 if newvalue
!= value
:
2816 self
._SetValue
(newvalue
)
2817 self
._SetInsertionPoint
(pos
)
2820 def _adjustKey(self
, pos
, key
):
2821 """ Apply control formatting to the key (e.g. convert to upper etc). """
2822 field
= self
._FindField
(pos
)
2823 if field
._forceupper
and key
in range(97,123):
2824 key
= ord( chr(key
).upper())
2826 if field
._forcelower
and key
in range(97,123):
2827 key
= ord( chr(key
).lower())
2832 def _adjustPos(self
, pos
, key
):
2834 Checks the current insertion point position and adjusts it if
2835 necessary to skip over non-editable characters.
2837 dbg('_adjustPos', pos
, key
, indent
=1)
2838 sel_start
, sel_to
= self
._GetSelection
()
2839 # If a numeric or decimal mask, and negatives allowed, reserve the first space for sign
2841 and pos
== self
._signpos
2842 and key
in (ord('-'), ord('+'), ord(' ')) ):
2845 if key
not in self
._nav
:
2846 field
= self
._FindField
(pos
)
2848 dbg('field._insertRight?', field
._insertRight
)
2849 if field
._insertRight
: # if allow right-insert
2850 start
, end
= field
._extent
2851 slice = self
._GetValue
()[start
:end
].strip()
2852 field_len
= end
- start
2853 if pos
== end
: # if cursor at right edge of field
2854 # if not filled or supposed to stay in field, keep current position
2855 if len(slice) < field_len
or not field
._moveOnFieldFull
:
2856 dbg('pos==end; len (slice) < field_len or not field._moveOnFieldFull')
2859 # if at start of control, move to right edge
2860 if sel_to
== sel_start
and self
._isTemplateChar
(pos
) and pos
!= end
:
2861 pos
= end
# move to right edge
2862 elif sel_start
<= start
and sel_to
== end
:
2863 # select to right edge of field - 1 (to replace char)
2865 self
._SetInsertionPoint
(pos
)
2867 self
._SetSelection
(sel_start
, pos
)
2869 elif self
._signOk
and sel_start
== 0: # if selected to beginning and signed,
2870 # adjust to past reserved sign position:
2871 pos
= self
._fields
[0]._extent
[0]
2872 self
._SetInsertionPoint
(pos
)
2874 self
._SetSelection
(pos
, sel_to
)
2876 pass # leave position/selection alone
2878 # else make sure the user is not trying to type over a template character
2879 # If they are, move them to the next valid entry position
2880 elif self
._isTemplateChar
(pos
):
2881 if( not field
._moveOnFieldFull
2882 and (not self
._signOk
2884 and field
._index
== 0
2885 and pos
> 0) ) ): # don't move to next field without explicit cursor movement
2888 # find next valid position
2889 pos
= self
._findNextEntry
(pos
)
2890 self
._SetInsertionPoint
(pos
)
2891 if pos
< sel_to
: # restore selection
2892 self
._SetSelection
(pos
, sel_to
)
2893 dbg('adjusted pos:', pos
, indent
=0)
2897 def _adjustDec(self
, candidate
=None):
2899 'Fixes' an floating point control. Collapses spaces, right-justifies, etc.
2901 dbg('wxMaskedEditMixin::_adjustDec, candidate = "%s"' % candidate
, indent
=1)
2902 lenOrd
,lenDec
= self
._mask
.split(self
._decimalChar
) ## Get ordinal, decimal lengths
2903 lenOrd
= len(lenOrd
)
2904 lenDec
= len(lenDec
)
2905 if candidate
is None: value
= self
._GetValue
()
2906 else: value
= candidate
2907 dbg('value = "%(value)s"' % locals(), 'len(value):', len(value
))
2908 ordStr
,decStr
= value
.split(self
._decimalChar
)
2910 ordStr
= self
._fields
[0]._AdjustField
(ordStr
)
2911 dbg('adjusted ordStr: "%s"' % ordStr
)
2912 lenOrd
= len(ordStr
)
2913 decStr
= decStr
+ ('0'*(lenDec
-len(decStr
))) # add trailing spaces to decimal
2915 dbg('ordStr "%(ordStr)s"' % locals())
2916 dbg('lenOrd:', lenOrd
)
2918 ordStr
= string
.rjust( ordStr
[-lenOrd
:], lenOrd
)
2919 dbg('right-justifed ordStr = "%(ordStr)s"' % locals())
2920 newvalue
= ordStr
+ self
._decimalChar
+ decStr
2922 if len(newvalue
) < len(self
._mask
):
2923 newvalue
= ' ' + newvalue
2924 signedvalue
= self
._getSignedValue
(newvalue
)[0]
2925 if signedvalue
is not None: newvalue
= signedvalue
2927 # Finally, align string with decimal position, left-padding with
2929 newdecpos
= newvalue
.find(self
._decimalChar
)
2930 if newdecpos
< self
._decimalpos
:
2931 padlen
= self
._decimalpos
- newdecpos
2932 newvalue
= string
.join([' ' * padlen
] + [newvalue
] ,'')
2933 dbg('newvalue = "%s"' % newvalue
)
2934 if candidate
is None:
2935 wxCallAfter(self
._SetValue
, newvalue
)
2940 def _adjustInt(self
, candidate
=None):
2941 """ 'Fixes' an integer control. Collapses spaces, right or left-justifies."""
2942 dbg("wxMaskedEditMixin::_adjustInt")
2943 lenInt
= len(self
._mask
)
2944 if candidate
is None: value
= self
._GetValue
()
2945 else: value
= candidate
2946 intStr
= self
._fields
[0]._AdjustField
(value
)
2947 intStr
= intStr
.strip() # drop extra spaces
2948 if self
._isNeg
and intStr
.find('-') == -1:
2949 intStr
= '-' + intStr
2950 elif self
._signOk
and intStr
.find('-') == -1:
2951 intStr
= ' ' + intStr
2953 if self
._fields
[0]._alignRight
: ## Only if right-alignment is enabled
2954 intStr
= intStr
.rjust( lenInt
)
2956 intStr
= intStr
.ljust( lenInt
)
2958 if candidate
is None:
2959 wxCallAfter(self
._SetValue
, intStr
)
2963 def _adjustDate(self
, candidate
=None, fixcentury
=False, force4digit_year
=False):
2965 'Fixes' a date control, expanding the year if it can.
2966 Applies various self-formatting options.
2968 dbg("wxMaskedEditMixin::_adjustDate", indent
=1)
2969 if candidate
is None: text
= self
._GetValue
()
2970 else: text
= candidate
2972 if self
._datestyle
== "YMD":
2977 dbg('getYear: "%s"' % getYear(text
, self
._datestyle
))
2978 year
= string
.replace( getYear( text
, self
._datestyle
),self
._fields
[year_field
]._fillChar
,"") # drop extra fillChars
2979 month
= getMonth( text
, self
._datestyle
)
2980 day
= getDay( text
, self
._datestyle
)
2981 dbg('self._datestyle:', self
._datestyle
, 'year:', year
, 'Month', month
, 'day:', day
)
2984 yearstart
= self
._dateExtent
- 4
2988 or (self
._GetInsertionPoint
() > yearstart
+1 and text
[yearstart
+2] == ' ')
2989 or (self
._GetInsertionPoint
() > yearstart
+2 and text
[yearstart
+3] == ' ') ) ):
2990 ## user entered less than four digits and changing fields or past point where we could
2991 ## enter another digit:
2995 dbg('bad year=', year
)
2996 year
= text
[yearstart
:self
._dateExtent
]
2998 if len(year
) < 4 and yearVal
:
3000 # Fix year adjustment to be less "20th century" :-) and to adjust heuristic as the
3002 now
= wxDateTime_Now()
3003 century
= (now
.GetYear() /100) * 100 # "this century"
3004 twodig_year
= now
.GetYear() - century
# "this year" (2 digits)
3005 # if separation between today's 2-digit year and typed value > 50,
3006 # assume last century,
3007 # else assume this century.
3009 # Eg: if 2003 and yearVal == 30, => 2030
3010 # if 2055 and yearVal == 80, => 2080
3011 # if 2010 and yearVal == 96, => 1996
3013 if abs(yearVal
- twodig_year
) > 50:
3014 yearVal
= (century
- 100) + yearVal
3016 yearVal
= century
+ yearVal
3017 year
= str( yearVal
)
3018 else: # pad with 0's to make a 4-digit year
3019 year
= "%04d" % yearVal
3020 if self
._4digityear
or force4digit_year
:
3021 text
= makeDate(year
, month
, day
, self
._datestyle
, text
) + text
[self
._dateExtent
:]
3022 dbg('newdate: "%s"' % text
, indent
=0)
3026 def _goEnd(self
, getPosOnly
=False):
3027 """ Moves the insertion point to the end of user-entry """
3028 dbg("wxMaskedEditMixin::_goEnd; getPosOnly:", getPosOnly
, indent
=1)
3029 text
= self
._GetValue
()
3030 for i
in range( min( len(self
._mask
)-1, len(text
)-1 ), -1, -1):
3031 if self
._isMaskChar
(i
):
3036 pos
= min(i
+1,len(self
._mask
))
3037 field
= self
._FindField
(pos
)
3038 start
, end
= field
._extent
3039 if field
._insertRight
and pos
< end
:
3041 dbg('next pos:', pos
)
3046 self
._SetInsertionPoint
(pos
)
3050 """ Moves the insertion point to the beginning of user-entry """
3051 dbg("wxMaskedEditMixin::_goHome", indent
=1)
3052 text
= self
._GetValue
()
3053 for i
in range(len(self
._mask
)):
3054 if self
._isMaskChar
(i
):
3056 self
._SetInsertionPoint
(max(i
,0))
3060 def _getAllowedChars(self
, pos
):
3061 """ Returns a string of all allowed user input characters for the provided
3062 mask character plus control options
3064 maskChar
= self
.maskdict
[pos
]
3065 okchars
= self
.maskchardict
[maskChar
] ## entry, get mask approved characters
3066 field
= self
._FindField
(pos
)
3067 if okchars
and field
._okSpaces
: ## Allow spaces?
3069 if okchars
and field
._includeChars
: ## any additional included characters?
3070 okchars
+= field
._includeChars
3071 ## dbg('okchars[%d]:' % pos, okchars)
3075 def _isMaskChar(self
, pos
):
3076 """ Returns True if the char at position pos is a special mask character (e.g. NCXaA#)
3078 if pos
< len(self
._mask
):
3079 return self
.ismasked
[pos
]
3084 def _isTemplateChar(self
,Pos
):
3085 """ Returns True if the char at position pos is a template character (e.g. -not- NCXaA#)
3087 if Pos
< len(self
._mask
):
3088 return not self
._isMaskChar
(Pos
)
3093 def _isCharAllowed(self
, char
, pos
, checkRegex
=False):
3094 """ Returns True if character is allowed at the specific position, otherwise False."""
3095 dbg('_isCharAllowed', char
, pos
, checkRegex
, indent
=1)
3096 field
= self
._FindField
(pos
)
3097 right_insert
= False
3099 if self
.controlInitialized
:
3100 sel_start
, sel_to
= self
._GetSelection
()
3102 sel_start
, sel_to
= pos
, pos
3104 if (field
._insertRight
or self
._ctrl
_constraints
._insertRight
):
3105 start
, end
= field
._extent
3106 if pos
== end
or (sel_start
, sel_to
) == field
._extent
:
3110 if self
._isTemplateChar
( pos
): ## if a template character, return empty
3114 if self
._isMaskChar
( pos
):
3115 okChars
= self
._getAllowedChars
(pos
)
3116 if self
._fields
[0]._groupdigits
and (self
._isInt
or (self
._isDec
and pos
< self
._decimalpos
)):
3117 okChars
+= self
._fields
[0]._groupChar
3118 if self
._signOk
and (self
._isInt
or (self
._isDec
and pos
< self
._decimalpos
)):
3120 ## dbg('%s in %s?' % (char, okChars), char in okChars)
3121 approved
= char
in okChars
3123 if approved
and checkRegex
:
3124 dbg("checking appropriate regex's")
3125 value
= self
._eraseSelection
(self
._GetValue
())
3127 newvalue
, newpos
= self
._insertKey
(char
, pos
+1, sel_start
, sel_to
, value
)
3129 newvalue
, newpos
= self
._insertKey
(char
, pos
, sel_start
, sel_to
, value
)
3130 dbg('newvalue: "%s"' % newvalue
)
3132 fields
= [self
._FindField
(pos
)] + [self
._ctrl
_constraints
]
3133 for field
in fields
: # includes fields[-1] == "ctrl_constraints"
3134 if field
._regexMask
and field
._filter
:
3135 dbg('checking vs. regex')
3136 start
, end
= field
._extent
3137 slice = newvalue
[start
:end
]
3138 approved
= (re
.match( field
._filter
, slice) is not None)
3139 dbg('approved?', approved
)
3140 if not approved
: break
3148 def _applyFormatting(self
):
3149 """ Apply formatting depending on the control's state.
3150 Need to find a way to call this whenever the value changes, in case the control's
3151 value has been changed or set programatically.
3154 dbg('wxMaskedEditMixin::_applyFormatting', indent
=1)
3156 # Handle negative numbers
3158 ## value = self._GetValue()
3159 text
, signpos
= self
._getSignedValue
()
3160 dbg('text: "%s", signpos:' % text
, signpos
)
3161 if not text
or text
[signpos
] != '-':
3163 dbg('supposedly negative, but no sign found; new sign:', self
._isNeg
)
3164 if text
and signpos
!= self
._signpos
:
3165 self
._signpos
= signpos
3166 elif text
and self
._valid
and not self
._isNeg
and text
[signpos
] == '-':
3169 if self
._signOk
and self
._isNeg
:
3170 dbg('setting foreground to', self
._signedForegroundColor
)
3171 self
.SetForegroundColour(self
._signedForegroundColor
)
3173 dbg('setting foreground to', self
._foregroundColor
)
3174 self
.SetForegroundColour(self
._foregroundColor
)
3179 dbg('setting background to', self
._emptyBackgroundColor
)
3180 self
.SetBackgroundColour(self
._emptyBackgroundColor
)
3182 self
.SetBackgroundColour(self
._validBackgroundColor
)
3184 dbg('invalid; coloring', self
._invalidBackgroundColor
)
3185 self
.SetBackgroundColour(self
._invalidBackgroundColor
) ## Change BG color if invalid
3188 dbg(indent
=0, suspend
=0)
3191 def _getAbsValue(self
, candidate
=None):
3192 """ Return an unsigned value (i.e. strip the '-' prefix if any).
3194 dbg('wxMaskedEditMixin::_getAbsValue; candidate="%s"' % candidate
, indent
=1)
3195 if candidate
is None: text
= self
._GetValue
()
3196 else: text
= candidate
3199 if self
._ctrl
_constraints
._alignRight
and self
._fields
[0]._fillChar
== ' ':
3200 signpos
= text
.find('-')
3202 rstripped_text
= text
.rstrip()
3203 signpos
= rstripped_text
.rfind(' ')
3204 dbg('signpos:', signpos
)
3207 abstext
= text
[:signpos
] + ' ' + text
[signpos
+1:]
3210 text
= self
._template
[0] + text
[1:]
3211 groupchar
= self
._fields
[0]._groupChar
3213 value
= long(text
.replace(groupchar
,''))
3215 dbg('invalid number', indent
=0)
3218 else: # decimal value
3220 groupchar
= self
._fields
[0]._groupChar
3221 value
= float(text
.replace(groupchar
,'').replace(self
._decimalChar
, '.'))
3223 signpos
= text
.find('-')
3224 text
= text
[:signpos
] + self
._template
[signpos
] + text
[signpos
+1:]
3226 # look backwards from the decimal point for the 1st non-digit
3227 dbg('decimal pos:', self
._decimalpos
)
3228 signpos
= self
._decimalpos
-1
3229 dbg('text: "%s"' % text
)
3230 dbg('text[%d]:' % signpos
, text
[signpos
])
3231 dbg('text[signpos] in list(string.digits) + [groupchar]?', text
[signpos
] in list(string
.digits
) + [groupchar
])
3232 while text
[signpos
] in list(string
.digits
) + [groupchar
] and signpos
> 0:
3234 dbg('text[%d]:' % signpos
, text
[signpos
])
3236 dbg('invalid number', indent
=0)
3239 dbg('abstext = "%s"' % text
, 'signpos:', signpos
)
3241 return text
, signpos
3244 def _getSignedValue(self
, candidate
=None):
3245 """ Return a signed value by adding a "-" prefix if the value
3246 is set to negative, or a space if positive.
3248 dbg('wxMaskedEditMixin::_getSignedValue; candidate="%s"' % candidate
, indent
=1)
3249 if candidate
is None: text
= self
._GetValue
()
3250 else: text
= candidate
3253 abstext
, signpos
= self
._getAbsValue
(text
)
3257 return abstext
, signpos
3259 if self
._isNeg
or text
[signpos
] == '-':
3263 if text
[signpos
] not in string
.digits
:
3264 text
= text
[:signpos
] + sign
+ text
[signpos
+1:]
3266 # this can happen if value passed is too big; sign assumed to be
3267 # in position 0, but if already filled with a digit, prepend sign...
3271 dbg('signedtext = "%s"' % text
, 'signpos:', signpos
)
3273 return text
, signpos
3276 def GetPlainValue(self
, candidate
=None):
3277 """ Returns control's value stripped of the template text.
3278 plainvalue = wxMaskedEditMixin.GetPlainValue()
3280 if candidate
is None: text
= self
._GetValue
()
3281 else: text
= candidate
3287 for idx
in range( len( self
._template
)):
3288 if self
._mask
[idx
] in maskchars
:
3291 if self
._isDec
or self
._isInt
:
3292 if self
._fields
[0]._alignRight
:
3293 lpad
= plain
.count(',')
3294 plain
= ' ' * lpad
+ plain
.replace(',','')
3296 plain
= plain
.replace(',','')
3298 if self
._signOk
and self
._isNeg
and plain
.count('-') == 0:
3299 # must be in reserved position; add to "plain value"
3302 return plain
.rstrip()
3305 def IsEmpty(self
, value
=None):
3307 Returns True if control is equal to an empty value.
3308 (Empty means all editable positions in the template == fillChar.)
3310 if value
is None: value
= self
._GetValue
()
3311 if value
== self
._template
and not self
._defaultValue
:
3312 ## dbg("IsEmpty? 1 (value == self._template and not self._defaultValue)")
3313 return True # (all mask chars == fillChar by defn)
3314 elif value
== self
._template
:
3316 for pos
in range(len(self
._template
)):
3317 ## dbg('isMaskChar(%(pos)d)?' % locals(), self._isMaskChar(pos))
3318 ## dbg('value[%(pos)d] != self._fillChar?' %locals(), value[pos] != self._fillChar[pos])
3319 if self
._isMaskChar
(pos
) and value
[pos
] not in (' ', self
._fillChar
[pos
]):
3321 ## dbg("IsEmpty? %(empty)d (do all mask chars == fillChar?)" % locals())
3324 ## dbg("IsEmpty? 0 (value doesn't match template)")
3328 def IsDefault(self
, value
=None):
3330 Returns True if the value specified (or the value of the control if not specified)
3331 is equal to the default value.
3333 if value
is None: value
= self
._GetValue
()
3334 return value
== self
._template
3337 def IsValid(self
, value
=None):
3338 """ Indicates whether the value specified (or the current value of the control
3339 if not specified) is considered valid."""
3340 ## dbg('wxMaskedEditMixin::IsValid("%s")' % value, indent=1)
3341 if value
is None: value
= self
._GetValue
()
3342 ret
= self
._CheckValid
(value
)
3347 def _eraseSelection(self
, value
=None, sel_start
=None, sel_to
=None):
3348 """ Used to blank the selection when inserting a new character. """
3349 dbg("wxMaskedEditMixin::_eraseSelection", indent
=1)
3350 if value
is None: value
= self
._GetValue
()
3351 if sel_start
is None or sel_to
is None:
3352 sel_start
, sel_to
= self
._GetSelection
() ## check for a range of selected text
3353 dbg('value: "%s"' % value
)
3354 dbg("current sel_start, sel_to:", sel_start
, sel_to
)
3356 newvalue
= list(value
)
3357 for i
in range(sel_start
, sel_to
):
3358 if self
._isMaskChar
(i
):
3359 field
= self
._FindField
(i
)
3363 newvalue
[i
] = self
._template
[i
]
3364 elif self
._signOk
and i
== 0 and newvalue
[i
] == '-':
3366 value
= string
.join(newvalue
,"")
3367 dbg('new value: "%s"' % value
)
3372 def _insertKey(self
, char
, pos
, sel_start
, sel_to
, value
):
3373 """ Handles replacement of the character at the current insertion point."""
3374 dbg('wxMaskedEditMixin::_insertKey', "\'" + char
+ "\'", pos
, sel_start
, sel_to
, '"%s"' % value
, indent
=1)
3376 text
= self
._eraseSelection
(value
)
3377 field
= self
._FindField
(pos
)
3378 start
, end
= field
._extent
3380 if field
._insertRight
:
3381 # special case; do right insertion if either whole field selected or cursor at right edge of field
3382 if pos
== end
or (sel_start
, sel_to
) == field
._extent
: # right edge insert
3383 fstr
= text
[start
:end
]
3384 erasable_chars
= [field
._fillChar
, ' ']
3385 if field
._padZero
: erasable_chars
.append('0')
3387 if fstr
[0] in erasable_chars
or (self
._signOk
and field
._index
== 0 and fstr
[0] == '-'):
3389 fstr
= fstr
[1:] + char
3390 dbg('field str: "%s"' % fstr
)
3391 newtext
= text
[:start
] + fstr
+ text
[end
:]
3392 if erased
== '-' and self
._signOk
:
3393 newtext
= '-' + newtext
[1:]
3394 dbg('newtext: "%s"' % newtext
)
3395 if self
._signOk
and field
._index
== 0: start
-= 1 # account for sign position
3396 if( field
._moveOnFieldFull
3397 and len(fstr
.lstrip()) == end
-start
): # if field now full
3398 newpos
= self
._findNextEntry
(end
) # go to next field
3400 newpos
= end
# else keep cursor at right edge
3403 before
= text
[0:pos
]
3404 after
= text
[pos
+1:]
3405 newtext
= before
+ char
+ after
3408 dbg('newtext: "%s"' % newtext
, 'newpos:', newpos
)
3411 return newtext
, newpos
3414 def _OnFocus(self
,event
):
3416 This event handler is currently necessary to work around new default
3417 behavior as of wxPython2.3.3;
3418 The TAB key auto selects the entire contents of the wxTextCtrl *after*
3419 the EVT_SET_FOCUS event occurs; therefore we can't query/adjust the selection
3420 *here*, because it hasn't happened yet. So to prevent this behavior, and
3421 preserve the correct selection when the focus event is not due to tab,
3422 we need to pull the following trick:
3424 dbg('wxMaskedEditMixin::_OnFocus')
3425 wxCallAfter(self
._fixSelection
)
3430 def _CheckValid(self
, candidate
=None):
3432 This is the default validation checking routine; It verifies that the
3433 current value of the control is a "valid value," and has the side
3434 effect of coloring the control appropriately.
3437 dbg('wxMaskedEditMixin::_CheckValid: candidate="%s"' % candidate
, indent
=1)
3438 oldValid
= self
._valid
3439 if candidate
is None: value
= self
._GetValue
()
3440 else: value
= candidate
3441 dbg('value: "%s"' % value
)
3443 valid
= True # assume True
3445 if not self
.IsDefault(value
) and self
._isDate
: ## Date type validation
3446 valid
= self
._validateDate
(value
)
3447 dbg("valid date?", valid
)
3449 elif not self
.IsDefault(value
) and self
._isTime
:
3450 valid
= self
._validateTime
(value
)
3451 dbg("valid time?", valid
)
3453 elif not self
.IsDefault(value
) and (self
._isInt
or self
._isDec
): ## Numeric type
3454 valid
= self
._validateNumeric
(value
)
3455 dbg("valid Number?", valid
)
3457 if valid
and not self
.IsDefault(value
):
3458 ## valid so far; ensure also allowed by any list or regex provided:
3459 valid
= self
._validateGeneric
(value
)
3460 dbg("valid value?", valid
)
3463 for field
in self
._fields
.values(): # (includes field -1, ie: "global setting")
3464 if field
._emptyInvalid
:
3465 start
, end
= field
._extent
3466 if field
.IsEmpty(value
[start
:end
]):
3470 dbg('valid?', valid
)
3474 self
._applyFormatting
()
3475 if self
._valid
!= oldValid
:
3476 dbg('validity changed: oldValid =',oldValid
,'newvalid =', self
._valid
)
3477 dbg('oldvalue: "%s"' % oldvalue
, 'newvalue: "%s"' % self
._GetValue
())
3478 dbg(indent
=0, suspend
=0)
3482 def _validateGeneric(self
, candidate
=None):
3483 """ Validate the current value using the provided list or Regex filter (if any).
3485 if candidate
is None:
3486 text
= self
._GetValue
()
3490 valid
= True # assume true
3491 for i
in [-1] + self
._field
_indices
: # process global constraints first:
3492 field
= self
._fields
[i
]
3493 start
, end
= field
._extent
3494 slice = text
[start
:end
]
3495 valid
= field
.IsValid(slice)
3502 def _validateNumeric(self
, candidate
=None):
3503 """ Validate that the value is within the specified range (if specified.)"""
3504 if candidate
is None: value
= self
._GetValue
()
3505 else: value
= candidate
3507 groupchar
= self
._fields
[0]._groupChar
3509 number
= float(value
.replace(groupchar
, '').replace(self
._decimalChar
, '.'))
3511 number
= int( value
.replace(groupchar
, ''))
3512 dbg('number:', number
)
3513 if self
._ctrl
_constraints
._hasRange
:
3514 valid
= self
._ctrl
_constraints
._rangeLow
<= number
<= self
._ctrl
_constraints
._rangeHigh
3517 groupcharpos
= value
.rfind(groupchar
)
3518 if groupcharpos
!= -1: # group char present
3519 dbg('groupchar found at', groupcharpos
)
3520 if self
._isDec
and groupcharpos
> self
._decimalpos
:
3521 # 1st one found on right-hand side is past decimal point
3522 dbg('groupchar in fraction; illegal')
3525 ord = value
[:self
._decimalpos
]
3529 parts
= ord.split(groupchar
)
3530 for i
in range(len(parts
)):
3531 if i
== 0 and abs(int(parts
[0])) > 999:
3532 dbg('group 0 too long; illegal')
3535 elif i
> 0 and (len(parts
[i
]) != 3 or ' ' in parts
[i
]):
3536 dbg('group %i (%s) not right size; illegal' % (i
, parts
[i
]))
3540 dbg('value not a valid number')
3545 def _validateDate(self
, candidate
=None):
3546 """ Validate the current date value using the provided Regex filter.
3547 Generally used for character types.BufferType
3549 dbg('wxMaskedEditMixin::_validateDate', indent
=1)
3550 if candidate
is None: value
= self
._GetValue
()
3551 else: value
= candidate
3552 dbg('value = "%s"' % value
)
3553 text
= self
._adjustDate
(value
, force4digit_year
=True) ## Fix the date up before validating it
3555 valid
= True # assume True until proven otherwise
3558 # replace fillChar in each field with space:
3559 datestr
= text
[0:self
._dateExtent
]
3561 field
= self
._fields
[i
]
3562 start
, end
= field
._extent
3563 fstr
= datestr
[start
:end
]
3564 fstr
.replace(field
._fillChar
, ' ')
3565 datestr
= datestr
[:start
] + fstr
+ datestr
[end
:]
3567 year
, month
, day
= getDateParts( datestr
, self
._datestyle
)
3569 dbg('self._dateExtent:', self
._dateExtent
)
3570 if self
._dateExtent
== 11:
3571 month
= charmonths_dict
[month
.lower()]
3575 dbg('year, month, day:', year
, month
, day
)
3578 dbg('cannot convert string to integer parts')
3581 dbg('cannot convert string to integer month')
3585 # use wxDateTime to unambiguously try to parse the date:
3586 # ### Note: because wxDateTime is *brain-dead* and expects months 0-11,
3587 # rather than 1-12, so handle accordingly:
3593 dbg("trying to create date from values day=%d, month=%d, year=%d" % (day
,month
,year
))
3594 dateHandler
= wxDateTimeFromDMY(day
,month
,year
)
3598 dbg('cannot convert string to valid date')
3604 # wxDateTime doesn't take kindly to leading/trailing spaces when parsing,
3605 # so we eliminate them here:
3606 timeStr
= text
[self
._dateExtent
+1:].strip() ## time portion of the string
3608 dbg('timeStr: "%s"' % timeStr
)
3610 checkTime
= dateHandler
.ParseTime(timeStr
)
3611 valid
= checkTime
== len(timeStr
)
3615 dbg('cannot convert string to valid time')
3616 if valid
: dbg('valid date')
3621 def _validateTime(self
, candidate
=None):
3622 """ Validate the current time value using the provided Regex filter.
3623 Generally used for character types.BufferType
3625 dbg('wxMaskedEditMixin::_validateTime', indent
=1)
3626 # wxDateTime doesn't take kindly to leading/trailing spaces when parsing,
3627 # so we eliminate them here:
3628 if candidate
is None: value
= self
._GetValue
().strip()
3629 else: value
= candidate
.strip()
3630 dbg('value = "%s"' % value
)
3631 valid
= True # assume True until proven otherwise
3633 dateHandler
= wxDateTime_Today()
3635 checkTime
= dateHandler
.ParseTime(value
)
3636 dbg('checkTime:', checkTime
, 'len(value)', len(value
))
3637 valid
= checkTime
== len(value
)
3642 dbg('cannot convert string to valid time')
3643 if valid
: dbg('valid time')
3648 def _OnKillFocus(self
,event
):
3649 """ Handler for EVT_KILL_FOCUS event.
3651 dbg('wxMaskedEditMixin::_OnKillFocus', 'isDate=',self
._isDate
, indent
=1)
3652 if self
._mask
and self
._IsEditable
():
3653 self
._AdjustField
(self
._GetInsertionPoint
())
3654 self
._CheckValid
() ## Call valid handler
3656 self
._LostFocus
() ## Provided for subclass use
3661 def _fixSelection(self
):
3663 This gets called after the TAB traversal selection is made, if the
3664 focus event was due to this, but before the EVT_LEFT_* events if
3665 the focus shift was due to a mouse event.
3667 The trouble is that, a priori, there's no explicit notification of
3668 why the focus event we received. However, the whole reason we need to
3669 do this is because the default behavior on TAB traveral in a wxTextCtrl is
3670 now to select the entire contents of the window, something we don't want.
3671 So we can *now* test the selection range, and if it's "the whole text"
3672 we can assume the cause, change the insertion point to the start of
3673 the control, and deselect.
3675 dbg('wxMaskedEditMixin::_fixSelection', indent
=1)
3676 if not self
._mask
or not self
._IsEditable
():
3680 sel_start
, sel_to
= self
._GetSelection
()
3681 dbg('sel_start, sel_to:', sel_start
, sel_to
, 'self.IsEmpty()?', self
.IsEmpty())
3683 if( sel_start
== 0 and sel_to
>= len( self
._mask
) #(can be greater in numeric controls because of reserved space)
3684 or self
.IsEmpty() or self
.IsDefault()):
3685 # This isn't normally allowed, and so assume we got here by the new
3686 # "tab traversal" behavior, so we need to reset the selection
3687 # and insertion point:
3688 dbg('entire text selected; resetting selection to start of control')
3690 if self
._FindField
(0)._selectOnFieldEntry
:
3691 edit_start
, edit_end
= self
._FindFieldExtent
(self
._GetInsertionPoint
())
3692 self
._SetSelection
(edit_start
, edit_end
)
3693 elif self
._fields
[0]._insertRight
:
3694 self
._SetInsertionPoint
(self
._fields
[0]._extent
[1])
3696 elif sel_start
== 0 and self
._GetValue
()[0] == '-' and (self
._isDec
or self
._isInt
) and self
._signOk
:
3697 dbg('control is empty; start at beginning after -')
3698 self
._SetInsertionPoint
(1) ## Move past minus sign space if signed
3699 if self
._FindField
(0)._selectOnFieldEntry
:
3700 edit_start
, edit_end
= self
._FindFieldExtent
(self
._GetInsertionPoint
())
3701 self
._SetSelection
(1, edit_end
)
3702 elif self
._fields
[0]._insertRight
:
3703 self
._SetInsertionPoint
(self
._fields
[0]._extent
[1])
3705 elif sel_start
> self
._goEnd
(getPosOnly
=True):
3706 dbg('cursor beyond the end of the user input; go to end of it')
3709 dbg('sel_start, sel_to:', sel_start
, sel_to
, 'len(self._mask):', len(self
._mask
))
3713 def _Keypress(self
,key
):
3714 """ Method provided to override OnChar routine. Return False to force
3715 a skip of the 'normal' OnChar process. Called before class OnChar.
3720 def _LostFocus(self
):
3721 """ Method provided for subclasses. _LostFocus() is called after
3722 the class processes its EVT_KILL_FOCUS event code.
3727 def _OnDoubleClick(self
, event
):
3728 """ selects field under cursor on dclick."""
3729 pos
= self
._GetInsertionPoint
()
3730 field
= self
._FindField
(pos
)
3731 start
, end
= field
._extent
3732 self
._SetInsertionPoint
(start
)
3733 self
._SetSelection
(start
, end
)
3737 """ Method provided for subclasses. Called by internal EVT_TEXT
3738 handler. Return False to override the class handler, True otherwise.
3745 Used to override the default Cut() method in base controls, instead
3746 copying the selection to the clipboard and then blanking the selection,
3747 leaving only the mask in the selected area behind.
3748 Note: _Cut (read "undercut" ;-) must be called from a Cut() override in the
3749 derived control because the mixin functions can't override a method of
3752 dbg("wxMaskedEditMixin::_Cut", indent
=1)
3753 value
= self
._GetValue
()
3754 dbg('current value: "%s"' % value
)
3755 sel_start
, sel_to
= self
._GetSelection
() ## check for a range of selected text
3756 dbg('selected text: "%s"' % value
[sel_start
:sel_to
].strip())
3757 do
= wxTextDataObject()
3758 do
.SetText(value
[sel_start
:sel_to
].strip())
3759 wxTheClipboard
.Open()
3760 wxTheClipboard
.SetData(do
)
3761 wxTheClipboard
.Close()
3763 wxCallAfter(self
._SetValue
, self
._eraseSelection
() )
3764 wxCallAfter(self
._SetInsertionPoint
, sel_start
)
3768 # WS Note: overriding Copy is no longer necessary given that you
3769 # can no longer select beyond the last non-empty char in the control.
3771 ## def _Copy( self ):
3773 ## Override the wxTextCtrl's .Copy function, with our own
3774 ## that does validation. Need to strip trailing spaces.
3776 ## sel_start, sel_to = self._GetSelection()
3777 ## select_len = sel_to - sel_start
3778 ## textval = wxTextCtrl._GetValue(self)
3780 ## do = wxTextDataObject()
3781 ## do.SetText(textval[sel_start:sel_to].strip())
3782 ## wxTheClipboard.Open()
3783 ## wxTheClipboard.SetData(do)
3784 ## wxTheClipboard.Close()
3787 def _getClipboardContents( self
):
3788 """ Subroutine for getting the current contents of the clipboard.
3790 do
= wxTextDataObject()
3791 wxTheClipboard
.Open()
3792 success
= wxTheClipboard
.GetData(do
)
3793 wxTheClipboard
.Close()
3798 # Remove leading and trailing spaces before evaluating contents
3799 return do
.GetText().strip()
3802 def _validatePaste(self
, paste_text
, sel_start
, sel_to
, raise_on_invalid
=False):
3804 Used by paste routine and field choice validation to see
3805 if a given slice of paste text is legal for the area in question:
3806 returns validity, replacement text, and extent of paste in
3810 dbg('wxMaskedEditMixin::_validatePaste("%(paste_text)s", %(sel_start)d, %(sel_to)d), raise_on_invalid? %(raise_on_invalid)d' % locals(), indent
=1)
3811 select_length
= sel_to
- sel_start
3812 maxlength
= select_length
3813 dbg('sel_to - sel_start:', maxlength
)
3815 maxlength
= len(self
._mask
) - sel_start
3816 dbg('maxlength:', maxlength
)
3817 length_considered
= len(paste_text
)
3818 if length_considered
> maxlength
:
3819 dbg('paste text will not fit into the control:', indent
=0)
3820 if raise_on_invalid
:
3821 raise ValueError('"%s" will not fit into the control "%s"' % (paste_text
, self
.name
))
3823 return False, None, None
3825 text
= self
._template
3826 dbg('length_considered:', length_considered
)
3829 replacement_text
= ""
3830 replace_to
= sel_start
3832 while valid_paste
and i
< length_considered
and replace_to
< len(self
._mask
):
3833 char
= paste_text
[i
]
3834 field
= self
._FindField
(replace_to
)
3835 if field
._forceupper
: char
= char
.upper()
3836 elif field
._forcelower
: char
= char
.lower()
3838 dbg('char:', "'"+char
+"'", 'i =', i
, 'replace_to =', replace_to
)
3839 dbg('self._isTemplateChar(%d)?' % replace_to
, self
._isTemplateChar
(replace_to
))
3840 if not self
._isTemplateChar
(replace_to
) and self
._isCharAllowed
( char
, replace_to
):
3841 replacement_text
+= char
3842 dbg("not template(%(replace_to)d) and charAllowed('%(char)s',%(replace_to)d)" % locals())
3843 dbg("replacement_text:", '"'+replacement_text
+'"')
3846 elif char
== self
._template
[replace_to
] or (i
== 0 and char
== '-' and self
._signOk
):
3847 replacement_text
+= char
3848 dbg("'%(char)s' == template(%(replace_to)d)" % locals())
3849 dbg("replacement_text:", '"'+replacement_text
+'"')
3853 next_entry
= self
._findNextEntry
(replace_to
, adjustInsert
=False)
3854 if next_entry
== replace_to
:
3857 replacement_text
+= self
._template
[replace_to
:next_entry
]
3858 dbg("skipping template; next_entry =", next_entry
)
3859 dbg("replacement_text:", '"'+replacement_text
+'"')
3860 replace_to
= next_entry
# so next_entry will be considered on next loop
3862 if not valid_paste
and raise_on_invalid
:
3863 dbg('raising exception')
3864 raise ValueError('"%s" cannot be inserted into the control "%s"' % (paste_text
, self
.name
))
3866 elif i
< len(paste_text
):
3868 if raise_on_invalid
:
3869 dbg('raising exception')
3870 raise ValueError('"%s" will not fit into the control "%s"' % (paste_text
, self
.name
))
3872 dbg('valid_paste?', valid_paste
)
3874 dbg('replacement_text: "%s"' % replacement_text
, 'replace to:', replace_to
)
3875 dbg(indent
=0, suspend
=0)
3876 return valid_paste
, replacement_text
, replace_to
3879 def _Paste( self
, value
=None, raise_on_invalid
=False, just_return_value
=False ):
3881 Used to override the base control's .Paste() function,
3882 with our own that does validation.
3883 Note: _Paste must be called from a Paste() override in the
3884 derived control because the mixin functions can't override a
3885 method of a sibling class.
3887 dbg('wxMaskedEditMixin::_Paste (value = "%s")' % value
, indent
=1)
3889 paste_text
= self
._getClipboardContents
()
3893 if paste_text
is not None:
3894 dbg('paste text:', paste_text
)
3895 # (conversion will raise ValueError if paste isn't legal)
3896 sel_start
, sel_to
= self
._GetSelection
()
3898 valid_paste
, replacement_text
, replace_to
= self
._validatePaste
(paste_text
, sel_start
, sel_to
, raise_on_invalid
)
3900 dbg('exception thrown', indent
=0)
3904 dbg('paste text not legal for the selection or portion of the control following the cursor;')
3908 text
= self
._eraseSelection
()
3910 new_text
= text
[:sel_start
] + replacement_text
+ text
[replace_to
:]
3912 new_text
= string
.ljust(new_text
,len(self
._mask
))
3913 dbg("new_text:", '"'+new_text
+'"')
3915 if not just_return_value
:
3919 wxCallAfter(self
._SetValue
, new_text
)
3920 new_pos
= sel_start
+ len(replacement_text
)
3921 wxCallAfter(self
._SetInsertionPoint
, new_pos
)
3924 elif just_return_value
:
3925 return self
._GetValue
()
3930 ## ---------- ---------- ---------- ---------- ---------- ---------- ----------
3932 class wxMaskedTextCtrl( wxTextCtrl
, wxMaskedEditMixin
):
3934 This is the primary derivation from wxMaskedEditMixin. It provides
3935 a general masked text control that can be configured with different
3939 def __init__( self
, parent
, id=-1, value
= '',
3940 pos
= wxDefaultPosition
,
3941 size
= wxDefaultSize
,
3942 style
= wxTE_PROCESS_TAB
,
3943 validator
=wxDefaultValidator
, ## placeholder provided for data-transfer logic
3944 name
= 'maskedTextCtrl',
3945 setupEventHandling
= True, ## setup event handling by default
3948 wxTextCtrl
.__init
__(self
, parent
, id, value
='',
3949 pos
=pos
, size
= size
,
3950 style
=style
, validator
=validator
,
3953 self
.controlInitialized
= True
3954 wxMaskedEditMixin
.__init
__( self
, name
, **kwargs
)
3955 self
._SetInitialValue
(value
)
3957 if setupEventHandling
:
3958 ## Setup event handlers
3959 EVT_SET_FOCUS( self
, self
._OnFocus
) ## defeat automatic full selection
3960 EVT_KILL_FOCUS( self
, self
._OnKillFocus
) ## run internal validator
3961 EVT_LEFT_DCLICK(self
, self
._OnDoubleClick
) ## select field under cursor on dclick
3962 EVT_KEY_DOWN( self
, self
._OnKeyDown
) ## capture control events not normally seen, eg ctrl-tab.
3963 EVT_CHAR( self
, self
._OnChar
) ## handle each keypress
3964 EVT_TEXT( self
, self
.GetId(), self
._OnTextChange
) ## color control appropriately
3968 return "<wxMaskedTextCtrl: %s>" % self
.GetValue()
3971 def _GetSelection(self
):
3973 Allow mixin to get the text selection of this control.
3974 REQUIRED by any class derived from wxMaskedEditMixin.
3976 return self
.GetSelection()
3978 def _SetSelection(self
, sel_start
, sel_to
):
3980 Allow mixin to set the text selection of this control.
3981 REQUIRED by any class derived from wxMaskedEditMixin.
3983 return self
.SetSelection( sel_start
, sel_to
)
3985 def SetSelection(self
, sel_start
, sel_to
):
3987 This is just for debugging...
3989 dbg("wxMaskedTextCtrl::SetSelection(%(sel_start)d, %(sel_to)d)" % locals())
3990 wxTextCtrl
.SetSelection(self
, sel_start
, sel_to
)
3993 def _GetInsertionPoint(self
):
3994 return self
.GetInsertionPoint()
3996 def _SetInsertionPoint(self
, pos
):
3997 self
.SetInsertionPoint(pos
)
3999 def SetInsertionPoint(self
, pos
):
4001 This is just for debugging...
4003 dbg("wxMaskedTextCtrl::SetInsertionPoint(%(pos)d)" % locals())
4004 wxTextCtrl
.SetInsertionPoint(self
, pos
)
4007 def _GetValue(self
):
4009 Allow mixin to get the raw value of the control with this function.
4010 REQUIRED by any class derived from wxMaskedEditMixin.
4012 return self
.GetValue()
4014 def _SetValue(self
, value
):
4016 Allow mixin to set the raw value of the control with this function.
4017 REQUIRED by any class derived from wxMaskedEditMixin.
4019 dbg('wxMaskedTextCtrl::_SetValue("%(value)s")' % locals(), indent
=1)
4020 wxTextCtrl
.SetValue(self
, value
)
4023 def SetValue(self
, value
):
4025 This function redefines the externally accessible .SetValue to be
4026 a smart "paste" of the text in question, so as not to corrupt the
4027 masked control. NOTE: this must be done in the class derived
4028 from the base wx control.
4030 dbg('wxMaskedTextCtrl::SetValue = "%s"' % value
, indent
=1)
4032 # empty previous contents, replacing entire value:
4033 self
._SetInsertionPoint
(0)
4034 self
._SetSelection
(0, len(self
._mask
))
4036 if( len(value
) < len(self
._mask
) # value shorter than control
4037 and (self
._isDec
or self
._isInt
) # and it's a numeric control
4038 and self
._ctrl
_constraints
._alignRight
): # and it's a right-aligned control
4039 # try to intelligently "pad out" the value to the right size:
4040 value
= self
._template
[0:len(self
._mask
) - len(value
)] + value
4041 dbg('padded value = "%s"' % value
)
4043 # make SetValue behave the same as if you had typed the value in:
4045 value
= self
._Paste
(value
, raise_on_invalid
=True, just_return_value
=True)
4047 self
._isNeg
= False # (clear current assumptions)
4048 value
= self
._adjustDec
(value
)
4050 self
._isNeg
= False # (clear current assumptions)
4051 value
= self
._adjustInt
(value
)
4052 elif self
._isDate
and not self
.IsValid(value
) and self
._4digityear
:
4053 value
= self
._adjustDate
(value
, fixcentury
=true
)
4055 # If date, year might be 2 digits vs. 4; try adjusting it:
4056 if self
._isDate
and self
._4digityear
:
4057 dateparts
= value
.split(' ')
4058 dateparts
[0] = self
._adjustDate
(dateparts
[0], fixcentury
=true
)
4059 value
= string
.join(dateparts
, ' ')
4060 dbg('adjusted value: "%s"' % value
)
4061 value
= self
._Paste
(value
, raise_on_invalid
=True, just_return_value
=True)
4065 self
._SetValue
(value
)
4071 Allow mixin to refresh the base control with this function.
4072 REQUIRED by any class derived from wxMaskedEditMixin.
4074 dbg('wxMaskedTextCtrl::_Refresh', indent
=1)
4075 wxTextCtrl
.Refresh(self
)
4081 This function redefines the externally accessible .Refresh() to
4082 validate the contents of the masked control as it refreshes.
4083 NOTE: this must be done in the class derived from the base wx control.
4085 dbg('wxMaskedTextCtrl::Refresh', indent
=1)
4091 def _IsEditable(self
):
4093 Allow mixin to determine if the base control is editable with this function.
4094 REQUIRED by any class derived from wxMaskedEditMixin.
4096 return wxTextCtrl
.IsEditable(self
)
4101 This function redefines the externally accessible .Cut to be
4102 a smart "erase" of the text in question, so as not to corrupt the
4103 masked control. NOTE: this must be done in the class derived
4104 from the base wx control.
4106 self
._Cut
() # call the mixin's Cut method
4111 This function redefines the externally accessible .Paste to be
4112 a smart "paste" of the text in question, so as not to corrupt the
4113 masked control. NOTE: this must be done in the class derived
4114 from the base wx control.
4116 self
._Paste
() # call the mixin's Paste method
4119 def IsModified(self
):
4121 This function overrides the raw wxTextCtrl method, because the
4122 masked edit mixin uses SetValue to change the value, which doesn't
4123 modify the state of this attribute. So, we keep track on each
4124 keystroke to see if the value changes, and if so, it's been
4127 return wxTextCtrl
.IsModified(self
) or self
.modified
4132 ## ---------- ---------- ---------- ---------- ---------- ---------- ----------
4133 ## Because calling SetSelection programmatically does not fire EVT_COMBOBOX
4134 ## events, we have to do it ourselves when we auto-complete.
4135 class wxMaskedComboBoxSelectEvent(wxPyCommandEvent
):
4136 def __init__(self
, id, selection
= 0, object=None):
4137 wxPyCommandEvent
.__init
__(self
, wxEVT_COMMAND_COMBOBOX_SELECTED
, id)
4139 self
.__selection
= selection
4140 self
.SetEventObject(object)
4142 def GetSelection(self
):
4143 """Retrieve the value of the control at the time
4144 this event was generated."""
4145 return self
.__selection
4148 class wxMaskedComboBox( wxComboBox
, wxMaskedEditMixin
):
4150 This masked edit control adds the ability to use a masked input
4151 on a combobox, and do auto-complete of such values.
4153 def __init__( self
, parent
, id=-1, value
= '',
4154 pos
= wxDefaultPosition
,
4155 size
= wxDefaultSize
,
4157 style
= wxCB_DROPDOWN
,
4158 validator
= wxDefaultValidator
,
4159 name
= "maskedComboBox",
4160 setupEventHandling
= True, ## setup event handling by default):
4164 # This is necessary, because wxComboBox currently provides no
4165 # method for determining later if this was specified in the
4166 # constructor for the control...
4167 self
.__readonly
= style
& wxCB_READONLY
== wxCB_READONLY
4169 kwargs
['choices'] = choices
## set up maskededit to work with choice list too
4171 ## Since combobox completion is case-insensitive, always validate same way
4172 if not kwargs
.has_key('compareNoCase'):
4173 kwargs
['compareNoCase'] = True
4175 wxMaskedEditMixin
.__init
__( self
, name
, **kwargs
)
4176 self
._choices
= self
._ctrl
_constraints
._choices
4177 dbg('self._choices:', self
._choices
)
4179 if self
._ctrl
_constraints
._alignRight
:
4180 choices
= [choice
.rjust(len(self
._mask
)) for choice
in choices
]
4182 choices
= [choice
.ljust(len(self
._mask
)) for choice
in choices
]
4184 wxComboBox
.__init
__(self
, parent
, id, value
='',
4185 pos
=pos
, size
= size
,
4186 choices
=choices
, style
=style|wxWANTS_CHARS
,
4187 validator
=validator
,
4190 self
.controlInitialized
= True
4192 # Set control font - fixed width by default
4196 self
.SetClientSize(self
.calcSize())
4199 # ensure value is width of the mask of the control:
4200 if self
._ctrl
_constraints
._alignRight
:
4201 value
= value
.rjust(len(self
._mask
))
4203 value
= value
.ljust(len(self
._mask
))
4206 self
.SetStringSelection(value
)
4208 self
._SetInitialValue
(value
)
4211 self
._SetKeycodeHandler
(WXK_UP
, self
.OnSelectChoice
)
4212 self
._SetKeycodeHandler
(WXK_DOWN
, self
.OnSelectChoice
)
4214 if setupEventHandling
:
4215 ## Setup event handlers
4216 EVT_SET_FOCUS( self
, self
._OnFocus
) ## defeat automatic full selection
4217 EVT_KILL_FOCUS( self
, self
._OnKillFocus
) ## run internal validator
4218 EVT_LEFT_DCLICK(self
, self
._OnDoubleClick
) ## select field under cursor on dclick
4219 EVT_CHAR( self
, self
._OnChar
) ## handle each keypress
4220 EVT_KEY_DOWN( self
, self
.OnKeyDown
) ## for special processing of up/down keys
4221 EVT_KEY_DOWN( self
, self
._OnKeyDown
) ## for processing the rest of the control keys
4222 ## (next in evt chain)
4223 EVT_TEXT( self
, self
.GetId(), self
._OnTextChange
) ## color control appropriately
4227 return "<wxMaskedComboBox: %s>" % self
.GetValue()
4230 def calcSize(self
, size
=None):
4232 Calculate automatic size if allowed; override base mixin function
4233 to account for the selector button.
4235 size
= self
._calcSize
(size
)
4236 return (size
[0]+20, size
[1])
4239 def _GetSelection(self
):
4241 Allow mixin to get the text selection of this control.
4242 REQUIRED by any class derived from wxMaskedEditMixin.
4244 return self
.GetMark()
4246 def _SetSelection(self
, sel_start
, sel_to
):
4248 Allow mixin to set the text selection of this control.
4249 REQUIRED by any class derived from wxMaskedEditMixin.
4251 return self
.SetMark( sel_start
, sel_to
)
4254 def _GetInsertionPoint(self
):
4255 return self
.GetInsertionPoint()
4257 def _SetInsertionPoint(self
, pos
):
4258 self
.SetInsertionPoint(pos
)
4261 def _GetValue(self
):
4263 Allow mixin to get the raw value of the control with this function.
4264 REQUIRED by any class derived from wxMaskedEditMixin.
4266 return self
.GetValue()
4268 def _SetValue(self
, value
):
4270 Allow mixin to set the raw value of the control with this function.
4271 REQUIRED by any class derived from wxMaskedEditMixin.
4273 # For wxComboBox, ensure that values are properly padded so that
4274 # if varying length choices are supplied, they always show up
4275 # in the window properly, and will be the appropriate length
4276 # to match the mask:
4277 if self
._ctrl
_constraints
._alignRight
:
4278 value
= value
.rjust(len(self
._mask
))
4280 value
= value
.ljust(len(self
._mask
))
4281 wxComboBox
.SetValue(self
, value
)
4283 def SetValue(self
, value
):
4285 This function redefines the externally accessible .SetValue to be
4286 a smart "paste" of the text in question, so as not to corrupt the
4287 masked control. NOTE: this must be done in the class derived
4288 from the base wx control.
4291 # empty previous contents, replacing entire value:
4292 self
._SetInsertionPoint
(0)
4293 self
._SetSelection
(0, len(self
._mask
))
4295 if( len(value
) < len(self
._mask
) # value shorter than control
4296 and (self
._isDec
or self
._isInt
) # and it's a numeric control
4297 and self
._ctrl
_constraints
._alignRight
): # and it's a right-aligned control
4298 # try to intelligently "pad out" the value to the right size:
4299 value
= self
._template
[0:len(self
._mask
) - len(value
)] + value
4300 dbg('padded value = "%s"' % value
)
4302 # make SetValue behave the same as if you had typed the value in:
4304 value
= self
._Paste
(value
, raise_on_invalid
=True, just_return_value
=True)
4306 self
._isNeg
= False # (clear current assumptions)
4307 value
= self
._adjustDec
(value
)
4309 self
._isNeg
= False # (clear current assumptions)
4310 value
= self
._adjustInt
(value
)
4311 elif self
._isDate
and not self
.IsValid(value
) and self
._4digityear
:
4312 value
= self
._adjustDate
(value
, fixcentury
=true
)
4314 # If date, year might be 2 digits vs. 4; try adjusting it:
4315 if self
._isDate
and self
._4digityear
:
4316 dateparts
= value
.split(' ')
4317 dateparts
[0] = self
._adjustDate
(dateparts
[0], fixcentury
=true
)
4318 value
= string
.join(dateparts
, ' ')
4319 dbg('adjusted value: "%s"' % value
)
4320 value
= self
._Paste
(value
, raise_on_invalid
=True, just_return_value
=True)
4324 self
._SetValue
(value
)
4328 Allow mixin to refresh the base control with this function.
4329 REQUIRED by any class derived from wxMaskedEditMixin.
4331 wxComboBox
.Refresh(self
)
4335 This function redefines the externally accessible .Refresh() to
4336 validate the contents of the masked control as it refreshes.
4337 NOTE: this must be done in the class derived from the base wx control.
4343 def _IsEditable(self
):
4345 Allow mixin to determine if the base control is editable with this function.
4346 REQUIRED by any class derived from wxMaskedEditMixin.
4348 return not self
.__readonly
4353 This function redefines the externally accessible .Cut to be
4354 a smart "erase" of the text in question, so as not to corrupt the
4355 masked control. NOTE: this must be done in the class derived
4356 from the base wx control.
4358 self
._Cut
() # call the mixin's Cut method
4363 This function redefines the externally accessible .Paste to be
4364 a smart "paste" of the text in question, so as not to corrupt the
4365 masked control. NOTE: this must be done in the class derived
4366 from the base wx control.
4368 self
._Paste
() # call the mixin's Paste method
4371 def Append( self
, choice
):
4373 This function override is necessary so we can keep track of any additions to the list
4374 of choices, because wxComboBox doesn't have an accessor for the choice list.
4376 if self
._ctrl
_constraints
._alignRight
:
4377 choice
= choice
.rjust(len(self
._mask
))
4379 choice
= choice
.ljust(len(self
._mask
))
4381 if self
._ctrl
_constraints
._choiceRequired
:
4382 choice
= choice
.lower().strip()
4383 self
._choices
.append(choice
)
4385 if not self
.IsValid(choice
):
4386 raise ValueError('%s: "%s" is not a valid value for the control as specified.' % (self
.name
, choice
))
4389 wxComboBox
.Append(self
, choice
)
4394 This function override is necessary so we can keep track of any additions to the list
4395 of choices, because wxComboBox doesn't have an accessor for the choice list.
4398 if self
._ctrl
_constraints
._choices
:
4399 self
.SetCtrlParameters(choices
=[])
4400 wxComboBox
.Clear(self
)
4405 This function is a hack to make up for the fact that wxComboBox has no
4406 method for returning the selected portion of its edit control. It
4407 works, but has the nasty side effect of generating lots of intermediate
4410 dbg(suspend
=1) # turn off debugging around this function
4411 dbg('wxMaskedComboBox::GetMark', indent
=1)
4412 sel_start
= sel_to
= self
.GetInsertionPoint()
4413 dbg("current sel_start:", sel_start
)
4414 value
= self
.GetValue()
4415 dbg('value: "%s"' % value
)
4417 self
._ignoreChange
= True # tell _OnTextChange() to ignore next event (if any)
4419 wxComboBox
.Cut(self
)
4420 newvalue
= self
.GetValue()
4421 dbg("value after Cut operation:", newvalue
)
4423 if newvalue
!= value
: # something was selected; calculate extent
4424 dbg("something selected")
4425 sel_to
= sel_start
+ len(value
) - len(newvalue
)
4426 wxComboBox
.SetValue(self
, value
) # restore original value and selection (still ignoring change)
4427 wxComboBox
.SetInsertionPoint(self
, sel_start
)
4428 wxComboBox
.SetMark(self
, sel_start
, sel_to
)
4430 self
._ignoreChange
= False # tell _OnTextChange() to pay attn again
4432 dbg('computed selection:', sel_start
, sel_to
, indent
=0, suspend
=0)
4433 return sel_start
, sel_to
4436 def OnKeyDown(self
, event
):
4438 This function is necessary because navigation and control key
4439 events do not seem to normally be seen by the wxComboBox's
4440 EVT_CHAR routine. (Tabs don't seem to be visible no matter
4443 if event
.GetKeyCode() in self
._nav
+ self
._control
:
4447 event
.Skip() # let mixin default KeyDown behavior occur
4450 def OnSelectChoice(self
, event
):
4452 This function appears to be necessary, because the processing done
4453 on the text of the control somehow interferes with the combobox's
4454 selection mechanism for the arrow keys.
4456 dbg('wxMaskedComboBox::OnSelectChoice', indent
=1)
4458 # force case-insensitive comparison for matching purposes:
4459 value
= self
.GetValue().lower().strip()
4460 if event
.GetKeyCode() == WXK_UP
:
4464 match_index
= self
._autoComplete
(direction
, self
._choices
, value
, self
._ctrl
_constraints
._compareNoCase
)
4465 if match_index
is not None:
4466 dbg('setting selection to', match_index
)
4467 self
.SetSelection(match_index
)
4468 # issue appropriate event to outside:
4469 self
.GetEventHandler().ProcessEvent(
4470 wxMaskedComboBoxSelectEvent( self
.GetId(), match_index
, self
) )
4472 keep_processing
= False
4474 pos
= self
._adjustPos
(self
._GetInsertionPoint
(), event
.GetKeyCode())
4475 field
= self
._FindField
(pos
)
4476 if self
.IsEmpty() or not field
._hasList
:
4477 dbg('selecting 1st value in list')
4478 self
.SetSelection(0)
4479 self
.GetEventHandler().ProcessEvent(
4480 wxMaskedComboBoxSelectEvent( self
.GetId(), 0, self
) )
4482 keep_processing
= False
4484 # attempt field-level auto-complete
4486 keep_processing
= self
._OnAutoCompleteField
(event
)
4488 return keep_processing
4491 ## ---------- ---------- ---------- ---------- ---------- ---------- ----------
4493 class wxIpAddrCtrl( wxMaskedTextCtrl
):
4495 This class is a particular type of wxMaskedTextCtrl that accepts
4496 and understands the semantics of IP addresses, reformats input
4497 as you move from field to field, and accepts '.' as a navigation
4498 character, so that typing an IP address can be done naturally.
4500 def __init__( self
, parent
, id=-1, value
= '',
4501 pos
= wxDefaultPosition
,
4502 size
= wxDefaultSize
,
4503 style
= wxTE_PROCESS_TAB
,
4504 validator
= wxDefaultValidator
,
4505 name
= 'wxIpAddrCtrl',
4506 setupEventHandling
= True, ## setup event handling by default
4509 if not kwargs
.has_key('mask'):
4510 kwargs
['mask'] = mask
= "###.###.###.###"
4511 if not kwargs
.has_key('formatcodes'):
4512 kwargs
['formatcodes'] = 'F_Sr<'
4513 if not kwargs
.has_key('validRegex'):
4514 kwargs
['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}"
4516 if not kwargs
.has_key('emptyInvalid'):
4517 kwargs
['emptyInvalid'] = True
4519 wxMaskedTextCtrl
.__init
__(
4520 self
, parent
, id=id, value
= value
,
4523 validator
= validator
,
4525 setupEventHandling
= setupEventHandling
,
4529 if not kwargs
.has_key('validRequired'):
4530 field_params
['validRequired'] = True
4532 field_params
['validRegex'] = "( | \d| \d |\d | \d\d|\d\d |\d \d|(1\d\d|2[0-4]\d|25[0-5]))"
4534 # require "valid" string; this prevents entry of any value > 255, but allows
4535 # intermediate constructions; overall control validation requires well-formatted value.
4536 field_params
['formatcodes'] = 'V'
4539 for i
in self
._field
_indices
:
4540 self
.SetFieldParameters(i
, **field_params
)
4542 # This makes '.' act like tab:
4543 self
._AddNavKey
('.', handler
=self
.OnDot
)
4544 self
._AddNavKey
('>', handler
=self
.OnDot
) # for "shift-."
4547 def OnDot(self
, event
):
4548 dbg('wxIpAddrCtrl::OnDot', indent
=1)
4549 pos
= self
._adjustPos
(self
._GetInsertionPoint
(), event
.GetKeyCode())
4550 oldvalue
= self
.GetValue()
4551 edit_start
, edit_end
, slice = self
._FindFieldExtent
(pos
, getslice
=True)
4552 if not event
.ShiftDown():
4554 # clip data in field to the right of pos, if adjusting fields
4555 # when not at delimeter; (assumption == they hit '.')
4556 newvalue
= oldvalue
[:pos
] + ' ' * (edit_end
- pos
) + oldvalue
[edit_end
:]
4557 self
._SetValue
(newvalue
)
4558 self
._SetInsertionPoint
(pos
)
4560 return self
._OnChangeField
(event
)
4564 def GetAddress(self
):
4565 value
= wxMaskedTextCtrl
.GetValue(self
)
4566 return value
.replace(' ','') # remove spaces from the value
4569 def _OnCtrl_S(self
, event
):
4570 dbg("wxIpAddrCtrl::_OnCtrl_S")
4572 print "value:", self
.GetAddress()
4575 def SetValue(self
, value
):
4576 dbg('wxIpAddrCtrl::SetValue(%s)' % str(value
), indent
=1)
4577 if type(value
) != types
.StringType
:
4578 raise ValueError('%s must be a string', str(value
))
4580 bValid
= True # assume true
4581 parts
= value
.split('.')
4587 if not 0 <= len(part
) <= 3:
4590 elif part
.strip(): # non-empty part
4592 j
= string
.atoi(part
)
4593 if not 0 <= j
<= 255:
4597 parts
[i
] = '%3d' % j
4602 # allow empty sections for SetValue (will result in "invalid" value,
4603 # but this may be useful for initializing the control:
4604 parts
[i
] = ' ' # convert empty field to 3-char length
4608 raise ValueError('value (%s) must be a string of form n.n.n.n where n is empty or in range 0-255' % str(value
))
4610 dbg('parts:', parts
)
4611 value
= string
.join(parts
, '.')
4612 wxMaskedTextCtrl
.SetValue(self
, value
)
4616 ## ---------- ---------- ---------- ---------- ---------- ---------- ----------
4617 ## these are helper subroutines:
4619 def movetodec( origvalue
, fmtstring
, neg
, addseparators
=False, sepchar
= ',',fillchar
=' '):
4620 """ addseparators = add separator character every three numerals if True
4622 fmt0
= fmtstring
.split('.')
4625 val
= origvalue
.split('.')[0].strip()
4626 ret
= fillchar
* (len(fmt1
)-len(val
)) + val
+ "." + "0" * len(fmt2
)
4629 return (ret
,len(fmt1
))
4632 def isDateType( fmtstring
):
4633 """ Checks the mask and returns True if it fits an allowed
4634 date or datetime format.
4636 dateMasks
= ("^##/##/####",
4648 reString
= "|".join(dateMasks
)
4649 filter = re
.compile( reString
)
4650 if re
.match(filter,fmtstring
): return True
4653 def isTimeType( fmtstring
):
4654 """ Checks the mask and returns True if it fits an allowed
4657 reTimeMask
= "^##:##(:##)?( (AM|PM))?"
4658 filter = re
.compile( reTimeMask
)
4659 if re
.match(filter,fmtstring
): return True
4663 def isDecimal( fmtstring
, decimalchar
):
4664 filter = re
.compile("[ ]?[#]+\%c[#]+\n" % decimalchar
)
4665 if re
.match(filter,fmtstring
+"\n"): return True
4669 def isInteger( fmtstring
):
4670 filter = re
.compile("[#]+\n")
4671 if re
.match(filter,fmtstring
+"\n"): return True
4675 def getDateParts( dateStr
, dateFmt
):
4676 if len(dateStr
) > 11: clip
= dateStr
[0:11]
4677 else: clip
= dateStr
4678 if clip
[-2] not in string
.digits
:
4679 clip
= clip
[:-1] # (got part of time; drop it)
4681 dateSep
= (('/' in clip
) * '/') + (('-' in clip
) * '-') + (('.' in clip
) * '.')
4682 slices
= clip
.split(dateSep
)
4683 if dateFmt
== "MDY":
4684 y
,m
,d
= (slices
[2],slices
[0],slices
[1]) ## year, month, date parts
4685 elif dateFmt
== "DMY":
4686 y
,m
,d
= (slices
[2],slices
[1],slices
[0]) ## year, month, date parts
4687 elif dateFmt
== "YMD":
4688 y
,m
,d
= (slices
[0],slices
[1],slices
[2]) ## year, month, date parts
4690 y
,m
,d
= None, None, None
4697 def getDateSepChar(dateStr
):
4698 clip
= dateStr
[0:10]
4699 dateSep
= (('/' in clip
) * '/') + (('-' in clip
) * '-') + (('.' in clip
) * '.')
4703 def makeDate( year
, month
, day
, dateFmt
, dateStr
):
4704 sep
= getDateSepChar( dateStr
)
4705 if dateFmt
== "MDY":
4706 return "%s%s%s%s%s" % (month
,sep
,day
,sep
,year
) ## year, month, date parts
4707 elif dateFmt
== "DMY":
4708 return "%s%s%s%s%s" % (day
,sep
,month
,sep
,year
) ## year, month, date parts
4709 elif dateFmt
== "YMD":
4710 return "%s%s%s%s%s" % (year
,sep
,month
,sep
,day
) ## year, month, date parts
4715 def getYear(dateStr
,dateFmt
):
4716 parts
= getDateParts( dateStr
, dateFmt
)
4719 def getMonth(dateStr
,dateFmt
):
4720 parts
= getDateParts( dateStr
, dateFmt
)
4723 def getDay(dateStr
,dateFmt
):
4724 parts
= getDateParts( dateStr
, dateFmt
)
4727 ## ---------- ---------- ---------- ---------- ---------- ---------- ----------
4728 class test(wxPySimpleApp
):
4730 from wxPython
.lib
.rcsizer
import RowColSizer
4731 self
.frame
= wxFrame( NULL
, -1, "wxMaskedEditMixin 0.0.7 Demo Page #1", size
= (700,600))
4732 self
.panel
= wxPanel( self
.frame
, -1)
4733 self
.sizer
= RowColSizer()
4738 id, id1
= wxNewId(), wxNewId()
4739 self
.command1
= wxButton( self
.panel
, id, "&Close" )
4740 self
.command2
= wxButton( self
.panel
, id1
, "&AutoFormats" )
4741 self
.sizer
.Add(self
.command1
, row
=0, col
=0, flag
=wxALL
, border
= 5)
4742 self
.sizer
.Add(self
.command2
, row
=0, col
=1, colspan
=2, flag
=wxALL
, border
= 5)
4743 EVT_BUTTON( self
.panel
, id, self
.onClick
)
4744 ## self.panel.SetDefaultItem(self.command1 )
4745 EVT_BUTTON( self
.panel
, id1
, self
.onClickPage
)
4747 self
.check1
= wxCheckBox( self
.panel
, -1, "Disallow Empty" )
4748 self
.check2
= wxCheckBox( self
.panel
, -1, "Highlight Empty" )
4749 self
.sizer
.Add( self
.check1
, row
=0,col
=3, flag
=wxALL
,border
=5 )
4750 self
.sizer
.Add( self
.check2
, row
=0,col
=4, flag
=wxALL
,border
=5 )
4751 EVT_CHECKBOX( self
.panel
, self
.check1
.GetId(), self
._onCheck
1 )
4752 EVT_CHECKBOX( self
.panel
, self
.check2
.GetId(), self
._onCheck
2 )
4755 label
= """Press ctrl-s in any field to output the value and plain value. Press ctrl-x to clear and re-set any field.
4756 Note that all controls have been auto-sized by including F in the format code.
4757 Try entering nonsensical or partial values in validated fields to see what happens (use ctrl-s to test the valid status)."""
4758 label2
= "\nNote that the State and Last Name fields are list-limited (Name:Smith,Jones,Williams)."
4760 self
.label1
= wxStaticText( self
.panel
, -1, label
)
4761 self
.label2
= wxStaticText( self
.panel
, -1, "Description")
4762 self
.label3
= wxStaticText( self
.panel
, -1, "Mask Value")
4763 self
.label4
= wxStaticText( self
.panel
, -1, "Format")
4764 self
.label5
= wxStaticText( self
.panel
, -1, "Reg Expr Val. (opt)")
4765 self
.label6
= wxStaticText( self
.panel
, -1, "wxMaskedEdit Ctrl")
4766 self
.label7
= wxStaticText( self
.panel
, -1, label2
)
4767 self
.label7
.SetForegroundColour("Blue")
4768 self
.label1
.SetForegroundColour("Blue")
4769 self
.label2
.SetFont(wxFont(9,wxSWISS
,wxNORMAL
,wxBOLD
))
4770 self
.label3
.SetFont(wxFont(9,wxSWISS
,wxNORMAL
,wxBOLD
))
4771 self
.label4
.SetFont(wxFont(9,wxSWISS
,wxNORMAL
,wxBOLD
))
4772 self
.label5
.SetFont(wxFont(9,wxSWISS
,wxNORMAL
,wxBOLD
))
4773 self
.label6
.SetFont(wxFont(9,wxSWISS
,wxNORMAL
,wxBOLD
))
4775 self
.sizer
.Add( self
.label1
, row
=1,col
=0,colspan
=7, flag
=wxALL
,border
=5)
4776 self
.sizer
.Add( self
.label7
, row
=2,col
=0,colspan
=7, flag
=wxALL
,border
=5)
4777 self
.sizer
.Add( self
.label2
, row
=3,col
=0, flag
=wxALL
,border
=5)
4778 self
.sizer
.Add( self
.label3
, row
=3,col
=1, flag
=wxALL
,border
=5)
4779 self
.sizer
.Add( self
.label4
, row
=3,col
=2, flag
=wxALL
,border
=5)
4780 self
.sizer
.Add( self
.label5
, row
=3,col
=3, flag
=wxALL
,border
=5)
4781 self
.sizer
.Add( self
.label6
, row
=3,col
=4, flag
=wxALL
,border
=5)
4783 # The following list is of the controls for the demo. Feel free to play around with
4786 #description mask excl format regexp range,list,initial
4787 ("Phone No", "(###) ###-#### x:###", "", 'F!^-R', "^\(\d\d\d\) \d\d\d-\d\d\d\d", (),[],''),
4788 ("Last Name Only", "C{14}", "", 'F {list}', '^[A-Z][a-zA-Z]+', (),('Smith','Jones','Williams'),''),
4789 ("Full Name", "C{14}", "", 'F_', '^[A-Z][a-zA-Z]+ [A-Z][a-zA-Z]+', (),[],''),
4790 ("Social Sec#", "###-##-####", "", 'F', "\d{3}-\d{2}-\d{4}", (),[],''),
4791 ("U.S. Zip+4", "#{5}-#{4}", "", 'F', "\d{5}-(\s{4}|\d{4})",(),[],''),
4792 ("U.S. State (2 char)\n(with default)","AA", "", 'F!', "[A-Z]{2}", (),states
, 'AZ'),
4793 ("Customer No", "\CAA-###", "", 'F!', "C[A-Z]{2}-\d{3}", (),[],''),
4794 ("Date (MDY) + Time\n(with default)", "##/##/#### ##:## AM", 'BCDEFGHIJKLMNOQRSTUVWXYZ','DFR!',"", (),[], r
'03/05/2003 12:00 AM'),
4795 ("Invoice Total", "#{9}.##", "", 'F-R,', "", (),[], ''),
4796 ("Integer (signed)\n(with default)", "#{6}", "", 'F-R', "", (),[], '0 '),
4797 ("Integer (unsigned)\n(with default), 1-399", "######", "", 'F', "", (1,399),[], '1 '),
4798 ("Month selector", "XXX", "", 'F', "", (),
4799 ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'],""),
4800 ("fraction selector","#/##", "", 'F', "^\d\/\d\d?", (),
4801 ['2/3', '3/4', '1/2', '1/4', '1/8', '1/16', '1/32', '1/64'], "")
4804 for control
in controls
:
4805 self
.sizer
.Add( wxStaticText( self
.panel
, -1, control
[0]),row
=rowcount
, col
=0,border
=5,flag
=wxALL
)
4806 self
.sizer
.Add( wxStaticText( self
.panel
, -1, control
[1]),row
=rowcount
, col
=1,border
=5, flag
=wxALL
)
4807 self
.sizer
.Add( wxStaticText( self
.panel
, -1, control
[3]),row
=rowcount
, col
=2,border
=5, flag
=wxALL
)
4808 self
.sizer
.Add( wxStaticText( self
.panel
, -1, control
[4][:20]),row
=rowcount
, col
=3,border
=5, flag
=wxALL
)
4810 if control
in controls
[:]:#-2]:
4811 newControl
= wxMaskedTextCtrl( self
.panel
, -1, "",
4813 excludeChars
= control
[2],
4814 formatcodes
= control
[3],
4816 validRegex
= control
[4],
4817 validRange
= control
[5],
4818 choices
= control
[6],
4819 defaultValue
= control
[7],
4821 if control
[6]: newControl
.SetCtrlParameters(choiceRequired
= True)
4823 newControl
= wxMaskedComboBox( self
.panel
, -1, "",
4824 choices
= control
[7],
4825 choiceRequired
= True,
4827 formatcodes
= control
[3],
4828 excludeChars
= control
[2],
4830 validRegex
= control
[4],
4831 validRange
= control
[5],
4833 self
.editList
.append( newControl
)
4835 self
.sizer
.Add( newControl
, row
=rowcount
,col
=4,flag
=wxALL
,border
=5)
4838 self
.sizer
.AddGrowableCol(4)
4840 self
.panel
.SetSizer(self
.sizer
)
4841 self
.panel
.SetAutoLayout(1)
4848 def onClick(self
, event
):
4851 def onClickPage(self
, event
):
4852 self
.page2
= test2(self
.frame
,-1,"")
4853 self
.page2
.Show(True)
4855 def _onCheck1(self
,event
):
4856 """ Set required value on/off """
4857 value
= event
.Checked()
4859 for control
in self
.editList
:
4860 control
.SetCtrlParameters(emptyInvalid
=True)
4863 for control
in self
.editList
:
4864 control
.SetCtrlParameters(emptyInvalid
=False)
4866 self
.panel
.Refresh()
4868 def _onCheck2(self
,event
):
4869 """ Highlight empty values"""
4870 value
= event
.Checked()
4872 for control
in self
.editList
:
4873 control
.SetCtrlParameters( emptyBackgroundColor
= 'Aquamarine')
4876 for control
in self
.editList
:
4877 control
.SetCtrlParameters( emptyBackgroundColor
= 'White')
4879 self
.panel
.Refresh()
4882 ## ---------- ---------- ---------- ---------- ---------- ---------- ----------
4884 class test2(wxFrame
):
4885 def __init__(self
, parent
, id, caption
):
4886 wxFrame
.__init
__( self
, parent
, id, "wxMaskedEdit control 0.0.7 Demo Page #2 -- AutoFormats", size
= (550,600))
4887 from wxPython
.lib
.rcsizer
import RowColSizer
4888 self
.panel
= wxPanel( self
, -1)
4889 self
.sizer
= RowColSizer()
4895 All these controls have been created by passing a single parameter, the AutoFormat code.
4896 The class contains an internal dictionary of types and formats (autoformats).
4897 To see a great example of validations in action, try entering a bad email address, then tab out."""
4899 self
.label1
= wxStaticText( self
.panel
, -1, label
)
4900 self
.label2
= wxStaticText( self
.panel
, -1, "Description")
4901 self
.label3
= wxStaticText( self
.panel
, -1, "AutoFormat Code")
4902 self
.label4
= wxStaticText( self
.panel
, -1, "wxMaskedEdit Control")
4903 self
.label1
.SetForegroundColour("Blue")
4904 self
.label2
.SetFont(wxFont(9,wxSWISS
,wxNORMAL
,wxBOLD
))
4905 self
.label3
.SetFont(wxFont(9,wxSWISS
,wxNORMAL
,wxBOLD
))
4906 self
.label4
.SetFont(wxFont(9,wxSWISS
,wxNORMAL
,wxBOLD
))
4908 self
.sizer
.Add( self
.label1
, row
=1,col
=0,colspan
=3, flag
=wxALL
,border
=5)
4909 self
.sizer
.Add( self
.label2
, row
=3,col
=0, flag
=wxALL
,border
=5)
4910 self
.sizer
.Add( self
.label3
, row
=3,col
=1, flag
=wxALL
,border
=5)
4911 self
.sizer
.Add( self
.label4
, row
=3,col
=2, flag
=wxALL
,border
=5)
4913 id, id1
= wxNewId(), wxNewId()
4914 self
.command1
= wxButton( self
.panel
, id, "&Close")
4915 self
.command2
= wxButton( self
.panel
, id1
, "&Print Formats")
4916 EVT_BUTTON( self
.panel
, id, self
.onClick
)
4917 self
.panel
.SetDefaultItem(self
.command1
)
4918 EVT_BUTTON( self
.panel
, id1
, self
.onClickPrint
)
4920 # The following list is of the controls for the demo. Feel free to play around with
4923 ("Phone No","USPHONEFULLEXT"),
4924 ("US Date + Time","USDATETIMEMMDDYYYY/HHMM"),
4925 ("US Date MMDDYYYY","USDATEMMDDYYYY/"),
4926 ("Time (with seconds)","TIMEHHMMSS"),
4927 ("Military Time\n(without seconds)","MILTIMEHHMM"),
4928 ("Social Sec#","USSOCIALSEC"),
4929 ("Credit Card","CREDITCARD"),
4930 ("Expiration MM/YY","EXPDATEMMYY"),
4931 ("Percentage","PERCENT"),
4932 ("Person's Age","AGE"),
4933 ("US Zip Code","USZIP"),
4934 ("US Zip+4","USZIPPLUS4"),
4935 ("Email Address","EMAIL"),
4936 ("IP Address", "(derived control wxIpAddrCtrl)")
4939 for control
in controls
:
4940 self
.sizer
.Add( wxStaticText( self
.panel
, -1, control
[0]),row
=rowcount
, col
=0,border
=5,flag
=wxALL
)
4941 self
.sizer
.Add( wxStaticText( self
.panel
, -1, control
[1]),row
=rowcount
, col
=1,border
=5, flag
=wxALL
)
4942 if control
in controls
[:-1]:
4943 self
.sizer
.Add( wxMaskedTextCtrl( self
.panel
, -1, "",
4944 autoformat
= control
[1],
4946 row
=rowcount
,col
=2,flag
=wxALL
,border
=5)
4948 self
.sizer
.Add( wxIpAddrCtrl( self
.panel
, -1, "", demo
=True ),
4949 row
=rowcount
,col
=2,flag
=wxALL
,border
=5)
4952 self
.sizer
.Add(self
.command1
, row
=0, col
=0, flag
=wxALL
, border
= 5)
4953 self
.sizer
.Add(self
.command2
, row
=0, col
=1, flag
=wxALL
, border
= 5)
4954 self
.sizer
.AddGrowableCol(3)
4956 self
.panel
.SetSizer(self
.sizer
)
4957 self
.panel
.SetAutoLayout(1)
4959 def onClick(self
, event
):
4962 def onClickPrint(self
, event
):
4963 for format
in masktags
.keys():
4964 sep
= "+------------------------+"
4965 print "%s\n%s \n Mask: %s \n RE Validation string: %s\n" % (sep
,format
, masktags
[format
][0], masktags
[format
][3])
4967 ## ---------- ---------- ---------- ---------- ---------- ---------- ----------
4969 if __name__
== "__main__":
4975 ## ===================================
4977 ## 1. WS: For some reason I don't understand, the control is generating two (2)
4978 ## EVT_TEXT events for every one (1) .SetValue() of the underlying control.
4979 ## I've been unsuccessful in determining why or in my efforts to make just one
4980 ## occur. So, I've added a hack to save the last seen value from the
4981 ## control in the EVT_TEXT handler, and if *different*, call event.Skip()
4982 ## to propagate it down the event chain, and let the application see it.
4984 ## 2. WS: wxMaskedComboBox is deficient in several areas, all having to do with the
4985 ## behavior of the underlying control that I can't fix. The problems are:
4986 ## a) The background coloring doesn't work in the text field of the control;
4987 ## instead, there's a only border around it that assumes the correct color.
4988 ## b) The control will not pass WXK_TAB to the event handler, no matter what
4989 ## I do, and there's no style wxCB_PROCESS_TAB like wxTE_PROCESS_TAB to
4990 ## indicate that we want these events. As a result, wxMaskedComboBox
4991 ## doesn't do the nice field-tabbing that wxMaskedTextCtrl does.
4992 ## c) Auto-complete had to be reimplemented for the control because programmatic
4993 ## setting of the value of the text field does not set up the auto complete
4994 ## the way that the control processing keystrokes does. (But I think I've
4995 ## implemented a fairly decent approximation.) Because of this the control
4996 ## also won't auto-complete on dropdown, and there's no event I can catch
4997 ## to work around this problem.
4998 ## d) There is no method provided for getting the selection; the hack I've
4999 ## implemented has its flaws, not the least of which is that due to the
5000 ## strategy that I'm using, the paste buffer is always replaced by the
5001 ## contents of the control's selection when in focus, on each keystroke;
5002 ## this makes it impossible to paste anything into a wxMaskedComboBox
5003 ## at the moment... :-(
5004 ## e) The other deficient behavior, likely induced by the workaround for (d),
5005 ## is that you can can't shift-left to select more than one character
5009 ## 3. WS: Controls on wxPanels don't seem to pass Shift-WXK_TAB to their
5010 ## EVT_KEY_DOWN or EVT_CHAR event handlers. Until this is fixed in
5011 ## wxWindows, shift-tab won't take you backwards through the fields of
5012 ## a wxMaskedTextCtrl like it should. Until then Shifted arrow keys will
5013 ## work like shift-tab and tab ought to.
5017 ## =============================##
5018 ## 1. Add Popup list for auto-completable fields that simulates combobox on individual
5019 ## fields. Example: City validates against list of cities, or zip vs zip code list.
5020 ## 2. Allow optional monetary symbols (eg. $, pounds, etc.) at front of a "decimal"
5022 ## 3. Fix shift-left selection for wxMaskedComboBox.
5023 ## 5. Transform notion of "decimal control" to be less "entire control"-centric,
5024 ## so that monetary symbols can be included and still have the appropriate
5025 ## semantics. (Big job, as currently written, but would make control even
5026 ## more useful for business applications.)
5030 ## ====================
5032 ## 1. Made it possible to configure grouping, decimal and shift-decimal characters,
5033 ## to make controls more usable internationally.
5034 ## 2. Added code to smart "adjust" value strings presented to .SetValue()
5035 ## for right-aligned numeric format controls if they are shorter than
5036 ## than the control width, prepending the missing portion, prepending control
5037 ## template left substring for the missing characters, so that setting
5038 ## numeric values is easier.
5039 ## 3. Renamed SetMaskParameters SetCtrlParameters() (with old name preserved
5040 ## for b-c), as this makes more sense.
5043 ## 1. Fixed .SetValue() to replace the current value, rather than the current
5044 ## selection. Also changed it to generate ValueError if presented with
5045 ## either a value which doesn't follow the format or won't fit. Also made
5046 ## set value adjust numeric and date controls as if user entered the value.
5047 ## Expanded doc explaining how SetValue() works.
5048 ## 2. Fixed EUDATE* autoformats, fixed IsDateType mask list, and added ability to
5049 ## use 3-char months for dates, and EUDATETIME, and EUDATEMILTIME autoformats.
5050 ## 3. Made all date autoformats automatically pick implied "datestyle".
5051 ## 4. Added IsModified override, since base wxTextCtrl never reports modified if
5052 ## .SetValue used to change the value, which is what the masked edit controls
5054 ## 5. Fixed bug in date position adjustment on 2 to 4 digit date conversion when
5055 ## using tab to "leave field" and auto-adjust.
5056 ## 6. Fixed bug in _isCharAllowed() for negative number insertion on pastes,
5057 ## and bug in ._Paste() that didn't account for signs in signed masks either.
5058 ## 7. Fixed issues with _adjustPos for right-insert fields causing improper
5059 ## selection/replacement of values
5060 ## 8. Fixed _OnHome handler to properly handle extending current selection to
5061 ## beginning of control.
5062 ## 9. Exposed all (valid) autoformats to demo, binding descriptions to
5064 ## 10. Fixed a couple of bugs in email regexp.
5065 ## 11. Made maskchardict an instance var, to make mask chars to be more
5066 ## amenable to international use.
5067 ## 12. Clarified meaning of '-' formatcode in doc.
5068 ## 13. Fixed a couple of coding bugs being flagged by Python2.1.
5069 ## 14. Fixed several issues with sign positioning, erasure and validity
5070 ## checking for "numeric" masked controls.
5071 ## 15. Added validation to wxIpAddrCtrl.SetValue().
5074 ## 1. Changed calling interface to use boolean "useFixedWidthFont" (True by default)
5075 ## vs. literal font facename, and use wxTELETYPE as the font family
5077 ## 2. Switched to use of dbg module vs. locally defined version.
5078 ## 3. Revamped entire control structure to use Field classes to hold constraint
5079 ## and formatting data, to make code more hierarchical, allow for more
5080 ## sophisticated masked edit construction.
5081 ## 4. Better strategy for managing options, and better validation on keywords.
5082 ## 5. Added 'V' format code, which requires that in order for a character
5083 ## to be accepted, it must result in a string that passes the validRegex.
5084 ## 6. Added 'S' format code which means "select entire field when navigating
5086 ## 7. Added 'r' format code to allow "right-insert" fields. (implies 'R'--right-alignment)
5087 ## 8. Added '<' format code to allow fields to require explicit cursor movement
5089 ## 9. Added validFunc option to other validation mechanisms, that allows derived
5090 ## classes to add dynamic validation constraints to the control.
5091 ## 10. Fixed bug in validatePaste code causing possible IndexErrors, and also
5092 ## fixed failure to obey case conversion codes when pasting.
5093 ## 11. Implemented '0' (zero-pad) formatting code, as it wasn't being done anywhere...
5094 ## 12. Removed condition from OnDecimalPoint, so that it always truncates right on '.'
5095 ## 13. Enhanced wxIpAddrCtrl to use right-insert fields, selection on field traversal,
5096 ## individual field validation to prevent field values > 255, and require explicit
5097 ## tab/. to change fields.
5098 ## 14. Added handler for left double-click to select field under cursor.
5099 ## 15. Fixed handling for "Read-only" styles.
5100 ## 16. Separated signedForegroundColor from 'R' style, and added foregroundColor
5101 ## attribute, for more consistent and controllable coloring.
5102 ## 17. Added retainFieldValidation parameter, allowing top-level constraints
5103 ## such as "validRequired" to be set independently of field-level equivalent.
5104 ## (needed in wxTimeCtrl for bounds constraints.)
5105 ## 18. Refactored code a bit, cleaned up and commented code more heavily, fixed
5106 ## some of the logic for setting/resetting parameters, eg. fillChar, defaultValue,
5108 ## 19. Fixed maskchar setting for upper/lowercase, to work in all locales.
5112 ## 1. Decimal point behavior restored for decimal and integer type controls:
5113 ## decimal point now trucates the portion > 0.
5114 ## 2. Return key now works like the tab character and moves to the next field,
5115 ## provided no default button is set for the form panel on which the control
5117 ## 3. Support added in _FindField() for subclasses controls (like timecontrol)
5118 ## to determine where the current insertion point is within the mask (i.e.
5119 ## which sub-'field'). See method documentation for more info and examples.
5120 ## 4. Added Field class and support for all constraints to be field-specific
5121 ## in addition to being globally settable for the control.
5122 ## Choices for each field are validated for length and pastability into
5123 ## the field in question, raising ValueError if not appropriate for the control.
5124 ## Also added selective additional validation based on individual field constraints.
5125 ## By default, SHIFT-WXK_DOWN, SHIFT-WXK_UP, WXK_PRIOR and WXK_NEXT all
5126 ## auto-complete fields with choice lists, supplying the 1st entry in
5127 ## the choice list if the field is empty, and cycling through the list in
5128 ## the appropriate direction if already a match. WXK_DOWN will also auto-
5129 ## complete if the field is partially completed and a match can be made.
5130 ## SHIFT-WXK_UP/DOWN will also take you to the next field after any
5131 ## auto-completion performed.
5132 ## 5. Added autoCompleteKeycodes=[] parameters for allowing further
5133 ## customization of the control. Any keycode supplied as a member
5134 ## of the _autoCompleteKeycodes list will be treated like WXK_NEXT. If
5135 ## requireFieldChoice is set, then a valid value from each non-empty
5136 ## choice list will be required for the value of the control to validate.
5137 ## 6. Fixed "auto-sizing" to be relative to the font actually used, rather
5138 ## than making assumptions about character width.
5139 ## 7. Fixed GetMaskParameter(), which was non-functional in previous version.
5140 ## 8. Fixed exceptions raised to provide info on which control had the error.
5141 ## 9. Fixed bug in choice management of wxMaskedComboBox.
5142 ## 10. Fixed bug in wxIpAddrCtrl causing traceback if field value was of
5143 ## the form '# #'. Modified control code for wxIpAddrCtrl so that '.'
5144 ## in the middle of a field clips the rest of that field, similar to
5145 ## decimal and integer controls.
5149 ## 1. "-" is a toggle for sign; "+" now changes - signed numerics to positive.
5150 ## 2. ',' in formatcodes now causes numeric values to be comma-delimited (e.g.333,333).
5151 ## 3. New support for selecting text within the control.(thanks Will Sadkin!)
5152 ## Shift-End and Shift-Home now select text as you would expect
5153 ## Control-Shift-End selects to the end of the mask string, even if value not entered.
5154 ## Control-A selects all *entered* text, Shift-Control-A selects everything in the control.
5155 ## 4. event.Skip() added to onKillFocus to correct remnants when running in Linux (contributed-
5156 ## for some reason I couldn't find the original email but thanks!!!)
5157 ## 5. All major key-handling code moved to their own methods for easier subclassing: OnHome,
5158 ## OnErase, OnEnd, OnCtrl_X, OnCtrl_A, etc.
5159 ## 6. Email and autoformat validations corrected using regex provided by Will Sadkin (thanks!).
5160 ## (The rest of the changes in this version were done by Will Sadkin with permission from Jeff...)
5161 ## 7. New mechanism for replacing default behavior for any given key, using
5162 ## ._SetKeycodeHandler(keycode, func) and ._SetKeyHandler(char, func) now available
5163 ## for easier subclassing of the control.
5164 ## 8. Reworked the delete logic, cut, paste and select/replace logic, as well as some bugs
5165 ## with insertion point/selection modification. Changed Ctrl-X to use standard "cut"
5166 ## semantics, erasing the selection, rather than erasing the entire control.
5167 ## 9. Added option for an "default value" (ie. the template) for use when a single fillChar
5168 ## is not desired in every position. Added IsDefault() function to mean "does the value
5169 ## equal the template?" and modified .IsEmpty() to mean "do all of the editable
5170 ## positions in the template == the fillChar?"
5171 ## 10. Extracted mask logic into mixin, so we can have both wxMaskedTextCtrl and wxMaskedComboBox,
5173 ## 11. wxMaskedComboBox now adds the capability to validate from list of valid values.
5174 ## Example: City validates against list of cities, or zip vs zip code list.
5175 ## 12. Fixed oversight in EVT_TEXT handler that prevented the events from being
5176 ## passed to the next handler in the event chain, causing updates to the
5177 ## control to be invisible to the parent code.
5178 ## 13. Added IPADDR autoformat code, and subclass wxIpAddrCtrl for controlling tabbing within
5179 ## the control, that auto-reformats as you move between cells.
5180 ## 14. Mask characters [A,a,X,#] can now appear in the format string as literals, by using '\'.
5181 ## 15. It is now possible to specify repeating masks, e.g. #{3}-#{3}-#{14}
5182 ## 16. Fixed major bugs in date validation, due to the fact that
5183 ## wxDateTime.ParseDate is too liberal, and will accept any form that
5184 ## makes any kind of sense, regardless of the datestyle you specified
5185 ## for the control. Unfortunately, the strategy used to fix it only
5186 ## works for versions of wxPython post 2.3.3.1, as a C++ assert box
5187 ## seems to show up on an invalid date otherwise, instead of a catchable
5189 ## 17. Enhanced date adjustment to automatically adjust heuristic based on
5190 ## current year, making last century/this century determination on
5191 ## 2-digit year based on distance between today's year and value;
5192 ## if > 50 year separation, assume last century (and don't assume last
5193 ## century is 20th.)
5194 ## 18. Added autoformats and support for including HHMMSS as well as HHMM for
5195 ## date times, and added similar time, and militaray time autoformats.
5196 ## 19. Enhanced tabbing logic so that tab takes you to the next field if the
5197 ## control is a multi-field control.
5198 ## 20. Added stub method called whenever the control "changes fields", that
5199 ## can be overridden by subclasses (eg. wxIpAddrCtrl.)
5200 ## 21. Changed a lot of code to be more functionally-oriented so side-effects
5201 ## aren't as problematic when maintaining code and/or adding features.
5202 ## Eg: IsValid() now does not have side-effects; it merely reflects the
5203 ## validity of the value of the control; to determine validity AND recolor
5204 ## the control, _CheckValid() should be used with a value argument of None.
5205 ## Similarly, made most reformatting function take an optional candidate value
5206 ## rather than just using the current value of the control, and only
5207 ## have them change the value of the control if a candidate is not specified.
5208 ## In this way, you can do validation *before* changing the control.
5209 ## 22. Changed validRequired to mean "disallow chars that result in invalid
5210 ## value." (Old meaning now represented by emptyInvalid.) (This was
5211 ## possible once I'd made the changes in (19) above.)
5212 ## 23. Added .SetMaskParameters and .GetMaskParameter methods, so they
5213 ## can be set/modified/retrieved after construction. Removed individual
5214 ## parameter setting functions, in favor of this mechanism, so that
5215 ## all adjustment of the control based on changing parameter values can
5216 ## be handled in one place with unified mechanism.
5217 ## 24. Did a *lot* of testing and fixing re: numeric values. Added ability
5218 ## to type "grouping char" (ie. ',') and validate as appropriate.
5219 ## 25. Fixed ZIPPLUS4 to allow either 5 or 4, but if > 5 must be 9.
5220 ## 26. Fixed assumption about "decimal or integer" masks so that they're only
5221 ## made iff there's no validRegex associated with the field. (This
5222 ## is so things like zipcodes which look like integers can have more
5223 ## restrictive validation (ie. must be 5 digits.)
5224 ## 27. Added a ton more doc strings to explain use and derivation requirements
5225 ## and did regularization of the naming conventions.
5226 ## 28. Fixed a range bug in _adjustKey preventing z from being handled properly.
5227 ## 29. Changed behavior of '.' (and shift-.) in numeric controls to move to
5228 ## reformat the value and move the next field as appropriate. (shift-'.',
5229 ## ie. '>' moves to the previous field.
5232 ## 1. Fixed regex bug that caused autoformat AGE to invalidate any age ending
5234 ## 2. New format character 'D' to trigger date type. If the user enters 2 digits in the
5235 ## year position, the control will expand the value to four digits, using numerals below
5236 ## 50 as 21st century (20+nn) and less than 50 as 20th century (19+nn).
5237 ## Also, new optional parameter datestyle = set to one of {MDY|DMY|YDM}
5238 ## 3. revalid parameter renamed validRegex to conform to standard for all validation
5239 ## parameters (see 2 new ones below).
5240 ## 4. New optional init parameter = validRange. Used only for int/dec (numeric) types.
5241 ## Allows the developer to specify a valid low/high range of values.
5242 ## 5. New optional init parameter = validList. Used for character types. Allows developer
5243 ## to send a list of values to the control to be used for specific validation.
5244 ## See the Last Name Only example - it is list restricted to Smith/Jones/Williams.
5245 ## 6. Date type fields now use wxDateTime's parser to validate the date and time.
5246 ## This works MUCH better than my kludgy regex!! Thanks to Robin Dunn for pointing
5247 ## me toward this solution!
5248 ## 7. Date fields now automatically expand 2-digit years when it can. For example,
5249 ## if the user types "03/10/67", then "67" will auto-expand to "1967". If a two-year
5250 ## date is entered it will be expanded in any case when the user tabs out of the
5252 ## 8. New class functions: SetValidBackgroundColor, SetInvalidBackgroundColor, SetEmptyBackgroundColor,
5253 ## SetSignedForeColor allow accessto override default class coloring behavior.
5254 ## 9. Documentation updated and improved.
5255 ## 10. Demo - page 2 is now a wxFrame class instead of a wxPyApp class. Works better.
5256 ## Two new options (checkboxes) - test highlight empty and disallow empty.
5257 ## 11. Home and End now work more intuitively, moving to the first and last user-entry
5258 ## value, respectively.
5259 ## 12. New class function: SetRequired(bool). Sets the control's entry required flag
5260 ## (i.e. disallow empty values if True).
5263 ## 1. get_plainValue method renamed to GetPlainValue following the wxWindows
5264 ## StudlyCaps(tm) standard (thanks Paul Moore). ;)
5265 ## 2. New format code 'F' causes the control to auto-fit (auto-size) itself
5266 ## based on the length of the mask template.
5267 ## 3. Class now supports "autoformat" codes. These can be passed to the class
5268 ## on instantiation using the parameter autoformat="code". If the code is in
5269 ## the dictionary, it will self set the mask, formatting, and validation string.
5270 ## I have included a number of samples, but I am hoping that someone out there
5271 ## can help me to define a whole bunch more.
5272 ## 4. I have added a second page to the demo (as well as a second demo class, test2)
5273 ## to showcase how autoformats work. The way they self-format and self-size is,
5274 ## I must say, pretty cool.
5275 ## 5. Comments added and some internal cosmetic revisions re: matching the code
5276 ## standards for class submission.
5277 ## 6. Regex validation is now done in real time - field turns yellow immediately
5278 ## and stays yellow until the entered value is valid
5279 ## 7. Cursor now skips over template characters in a more intuitive way (before the
5281 ## 8. Change, Keypress and LostFocus methods added for convenience of subclasses.
5282 ## Developer may use these methods which will be called after EVT_TEXT, EVT_CHAR,
5283 ## and EVT_KILL_FOCUS, respectively.
5284 ## 9. Decimal and numeric handlers have been rewritten and now work more intuitively.
5287 ## 1. New .IsEmpty() method returns True if the control's value is equal to the
5288 ## blank template string
5289 ## 2. Control now supports a new init parameter: revalid. Pass a regular expression
5290 ## that the value will have to match when the control loses focus. If invalid,
5291 ## the control's BackgroundColor will turn yellow, and an internal flag is set (see next).
5292 ## 3. Demo now shows revalid functionality. Try entering a partial value, such as a
5293 ## partial social security number.
5294 ## 4. New .IsValid() value returns True if the control is empty, or if the value matches
5295 ## the revalid expression. If not, .IsValid() returns False.
5296 ## 5. Decimal values now collapse to decimal with '.00' on losefocus if the user never
5297 ## presses the decimal point.
5298 ## 6. Cursor now goes to the beginning of the field if the user clicks in an
5299 ## "empty" field intead of leaving the insertion point in the middle of the
5301 ## 7. New "N" mask type includes upper and lower chars plus digits. a-zA-Z0-9.
5302 ## 8. New formatcodes init parameter replaces other init params and adds functions.
5303 ## String passed to control on init controls:
5307 ## R Show negative #s in red
5309 ## - Signed numerals
5310 ## 0 Numeric fields get leading zeros
5311 ## 9. Ctrl-X in any field clears the current value.
5312 ## 10. Code refactored and made more modular (esp in OnChar method). Should be more
5313 ## easy to read and understand.
5314 ## 11. Demo enhanced.
5315 ## 12. Now has _doc_.
5318 ## 1. GetPlainValue() now returns the value without the template characters;
5319 ## so, for example, a social security number (123-33-1212) would return as
5320 ## 123331212; also removes white spaces from numeric/decimal values, so
5321 ## "- 955.32" is returned "-955.32". Press ctrl-S to see the plain value.
5322 ## 2. Press '.' in an integer style masked control and truncate any trailing digits.
5323 ## 3. Code moderately refactored. Internal names improved for clarity. Additional
5324 ## internal documentation.
5325 ## 4. Home and End keys now supported to move cursor to beginning or end of field.
5326 ## 5. Un-signed integers and decimals now supported.
5327 ## 6. Cosmetic improvements to the demo.
5328 ## 7. Class renamed to wxMaskedTextCtrl.
5329 ## 8. Can now specify include characters that will override the basic
5330 ## controls: for example, includeChars = "@." for email addresses
5331 ## 9. Added mask character 'C' -> allow any upper or lowercase character
5332 ## 10. .SetSignColor(str:color) sets the foreground color for negative values
5333 ## in signed controls (defaults to red)
5334 ## 11. Overview documentation written.
5337 ## 1. Tab now works properly when pressed in last position
5338 ## 2. Decimal types now work (e.g. #####.##)
5339 ## 3. Signed decimal or numeric values supported (i.e. negative numbers)
5340 ## 4. Negative decimal or numeric values now can show in red.
5341 ## 5. Can now specify an "exclude list" with the excludeChars parameter.
5342 ## See date/time formatted example - you can only enter A or P in the
5343 ## character mask space (i.e. AM/PM).
5344 ## 6. Backspace now works properly, including clearing data from a selected
5345 ## region but leaving template characters intact. Also delete key.
5346 ## 7. Left/right arrows now work properly.
5347 ## 8. Removed EventManager call from test so demo should work with wxPython 2.3.3