From 5fe636c6e23828ca6634a9358c3a6d318e89559c Mon Sep 17 00:00:00 2001 From: Robin Dunn Date: Tue, 5 Dec 2006 18:57:31 +0000 Subject: [PATCH] wx.lib.masked: Patch from Will Sadkin. Includes Unicode fixes, plus more helpful exceptions and ability to designate fields in mask without intervening fixed characters. git-svn-id: https://svn.wxwidgets.org/svn/wx/wxWidgets/trunk@43827 c3d73ce0-8a6f-49c7-b76d-6d57e0e08775 --- wxPython/demo/MaskedEditControls.py | 4 +- wxPython/docs/CHANGES.txt | 3 + wxPython/wx/lib/masked/__init__.py | 6 + wxPython/wx/lib/masked/maskededit.py | 244 ++++++++++++++++++++------- 4 files changed, 192 insertions(+), 65 deletions(-) diff --git a/wxPython/demo/MaskedEditControls.py b/wxPython/demo/MaskedEditControls.py index b88f53b07e..5e83e9cc3b 100644 --- a/wxPython/demo/MaskedEditControls.py +++ b/wxPython/demo/MaskedEditControls.py @@ -633,9 +633,11 @@ def RunStandalone(): app.MainLoop() #---------------------------------------------------------------------------- import wx.lib.masked.maskededit as maskededit +# strip out module header used for pydoc: +demodoc = '\n'.join(maskededit.__doc__.split('\n')[2:]) overview = """

-""" + maskededit.__doc__ + """
+""" + demodoc + """
 
""" diff --git a/wxPython/docs/CHANGES.txt b/wxPython/docs/CHANGES.txt index ae78fc3ef1..b862f7020c 100644 --- a/wxPython/docs/CHANGES.txt +++ b/wxPython/docs/CHANGES.txt @@ -46,6 +46,9 @@ Added some modules from Riaan Booysen: * An I18N sample for the demo. +wx.lib.masked: Patch from Will Sadkin. Includes Unicode fixes, plus +more helpful exceptions and ability to designate fields in mask +without intervening fixed characters. diff --git a/wxPython/wx/lib/masked/__init__.py b/wxPython/wx/lib/masked/__init__.py index 8e2c018745..0f8824bd42 100644 --- a/wxPython/wx/lib/masked/__init__.py +++ b/wxPython/wx/lib/masked/__init__.py @@ -9,6 +9,12 @@ # Copyright: (c) 2004 # License: wxWidgets license #---------------------------------------------------------------------- +__author__ = "Will Sadkin " +__date__ = "02 Dec 2006, 19:00 GMT-05:00" +__version__ = "1.11" +__doc__ = """\ +package providing "masked edit" controls, allowing characters within a data entry control to remain fixed, and providing fine-grain control over allowed user input. +""" # import relevant external symbols into package namespace: from maskededit import * diff --git a/wxPython/wx/lib/masked/maskededit.py b/wxPython/wx/lib/masked/maskededit.py index 7127285046..77776d7737 100644 --- a/wxPython/wx/lib/masked/maskededit.py +++ b/wxPython/wx/lib/masked/maskededit.py @@ -1,10 +1,10 @@ #---------------------------------------------------------------------------- # Name: maskededit.py # Authors: Jeff Childers, Will Sadkin -# Email: jchilders_98@yahoo.com, wsadkin@nameconnector.com +# Email: jchilders_98@yahoo.com, wsadkin@parlancecorp.com # Created: 02/11/2003 # Copyright: (c) 2003 by Jeff Childers, Will Sadkin, 2003 -# Portions: (c) 2002 by Will Sadkin, 2002-2003 +# Portions: (c) 2002 by Will Sadkin, 2002-2006 # RCS-ID: $Id$ # License: wxWindows license #---------------------------------------------------------------------------- @@ -50,7 +50,9 @@ # o wxTimeCtrl -> TimeCtrl # -"""\ +__doc__ = """\ +contains MaskedEditMixin class that drives all the other masked controls. + ==================== Masked Edit Overview ==================== @@ -88,11 +90,11 @@ masked.Ctrl: will be masked.TextCtrl. Each of the above classes has its own set of arguments, but masked.Ctrl - provides a single "unified" interface for masked controls. Those for - masked.TextCtrl, masked.ComboBox and masked.IpAddrCtrl are all documented - below; the others have their own demo pages and interface descriptions. - (See end of following discussion for how to configure the wx.MaskedCtrl() - to select the above control types.) + provides a single "unified" interface for masked controls. + +What follows is a description of how to configure the generic masked.TextCtrl +and masked.ComboBox; masked.NumCtrl and masked.TimeCtrl have their own demo +pages and interface descriptions. ========================= @@ -110,8 +112,12 @@ mask a Allow lowercase letters only C Allow any letter, upper or lower X Allow string.letters, string.punctuation, string.digits - & Allow string.punctuation only - \* Allow any ansi character + & Allow string.punctuation only (doesn't include all unicode symbols) + \* Allow any visible character + | explicit field boundary (takes no space in the control; allows mix + of adjacent mask characters to be treated as separate fields, + eg: '&|###' means "field 0 = '&', field 1 = '###'", but there's + no fixed characters in between. ========= ========================================================== @@ -127,8 +133,13 @@ mask import locale locale.setlocale(locale.LC_ALL, '') - The controls now also support (by popular demand) all "ansi" chars, - that is, all ascii codes between 32 and 255, by use of the * mask character. + The controls now also support (by popular demand) all "visible" characters, + by use of the * mask character, including unicode characters above + the standard ANSI keycode range. + Note: As string.punctuation doesn't typically include all unicode + symbols, you will have to use includechars to get some of these into + otherwise restricted positions in your control, such as those specified + with &. Using these mask characters, a variety of template masks can be built. See the demo for some other common examples include date+time, social security @@ -323,6 +334,9 @@ to individual fields: of the nature of the validation and control of input. However, you can supply one to provide data transfer routines for the controls. + raiseOnInvalidPaste False by default; normally a bad paste simply is ignored with a bell; + if True, this will cause a ValueError exception to be thrown, + with the .value attribute of the exception containing the bad value. ===================== ================================================================== @@ -835,17 +849,53 @@ control = ( WXK_CTRL_X, WXK_CTRL_Z ) +# Because unicode can go over the ansi character range, we need to explicitly test +# for all non-visible keystrokes, rather than just assuming a particular range for +# visible characters: +wx_control_keycodes = range(32) + list(nav) + list(control) + [ + wx.WXK_START, wx.WXK_LBUTTON, wx.WXK_RBUTTON, wx.WXK_CANCEL, wx.WXK_MBUTTON, + wx.WXK_CLEAR, wx.WXK_SHIFT, wx.WXK_CONTROL, wx.WXK_MENU, wx.WXK_PAUSE, + wx.WXK_CAPITAL, wx.WXK_SELECT, wx.WXK_PRINT, wx.WXK_EXECUTE, wx.WXK_SNAPSHOT, + wx.WXK_HELP, wx.WXK_NUMPAD0, wx.WXK_NUMPAD1, wx.WXK_NUMPAD2, wx.WXK_NUMPAD3, + wx.WXK_NUMPAD4, wx.WXK_NUMPAD5, wx.WXK_NUMPAD6, wx.WXK_NUMPAD7, wx.WXK_NUMPAD8, + wx.WXK_NUMPAD9, wx.WXK_MULTIPLY, wx.WXK_ADD, wx.WXK_SEPARATOR, wx.WXK_SUBTRACT, + wx.WXK_DECIMAL, wx.WXK_DIVIDE, wx.WXK_F1, wx.WXK_F2, wx.WXK_F3, wx.WXK_F4, + wx.WXK_F5, wx.WXK_F6, wx.WXK_F7, wx.WXK_F8, wx.WXK_F9, wx.WXK_F10, wx.WXK_F11, + wx.WXK_F12, wx.WXK_F13, wx.WXK_F14, wx.WXK_F15, wx.WXK_F16, wx.WXK_F17, + wx.WXK_F18, wx.WXK_F19, wx.WXK_F20, wx.WXK_F21, wx.WXK_F22, wx.WXK_F23, + wx.WXK_F24, wx.WXK_NUMLOCK, wx.WXK_SCROLL, wx.WXK_PAGEUP, wx.WXK_PAGEDOWN, + wx.WXK_NUMPAD_SPACE, wx.WXK_NUMPAD_TAB, wx.WXK_NUMPAD_ENTER, wx.WXK_NUMPAD_F1, + wx.WXK_NUMPAD_F2, wx.WXK_NUMPAD_F3, wx.WXK_NUMPAD_F4, wx.WXK_NUMPAD_HOME, + wx.WXK_NUMPAD_LEFT, wx.WXK_NUMPAD_UP, wx.WXK_NUMPAD_RIGHT, wx.WXK_NUMPAD_DOWN, + wx.WXK_NUMPAD_PRIOR, wx.WXK_NUMPAD_PAGEUP, wx.WXK_NUMPAD_NEXT, wx.WXK_NUMPAD_PAGEDOWN, + wx.WXK_NUMPAD_END, wx.WXK_NUMPAD_BEGIN, wx.WXK_NUMPAD_INSERT, wx.WXK_NUMPAD_DELETE, + wx.WXK_NUMPAD_EQUAL, wx.WXK_NUMPAD_MULTIPLY, wx.WXK_NUMPAD_ADD, wx.WXK_NUMPAD_SEPARATOR, + wx.WXK_NUMPAD_SUBTRACT, wx.WXK_NUMPAD_DECIMAL, wx.WXK_NUMPAD_DIVIDE, wx.WXK_WINDOWS_LEFT, + wx.WXK_WINDOWS_RIGHT, wx.WXK_WINDOWS_MENU, wx.WXK_COMMAND, + # Hardware-specific buttons + wx.WXK_SPECIAL1, wx.WXK_SPECIAL2, wx.WXK_SPECIAL3, wx.WXK_SPECIAL4, wx.WXK_SPECIAL5, + wx.WXK_SPECIAL6, wx.WXK_SPECIAL7, wx.WXK_SPECIAL8, wx.WXK_SPECIAL9, wx.WXK_SPECIAL10, + wx.WXK_SPECIAL11, wx.WXK_SPECIAL12, wx.WXK_SPECIAL13, wx.WXK_SPECIAL14, wx.WXK_SPECIAL15, + wx.WXK_SPECIAL16, wx.WXK_SPECIAL17, wx.WXK_SPECIAL18, wx.WXK_SPECIAL19, wx.WXK_SPECIAL20 + ] + ## ---------- ---------- ---------- ---------- ---------- ---------- ---------- ## Constants for masking. This is where mask characters ## are defined. ## maskchars used to identify valid mask characters from all others -## #- allow numeric 0-9 only -## A- allow uppercase only. Combine with forceupper to force lowercase to upper -## a- allow lowercase only. Combine with forcelower to force upper to lowercase -## X- allow any character (string.letters, string.punctuation, string.digits) +## # - allow numeric 0-9 only +## A - allow uppercase only. Combine with forceupper to force lowercase to upper +## a - allow lowercase only. Combine with forcelower to force upper to lowercase +## C - allow any letter, upper or lower +## X - allow string.letters, string.punctuation, string.digits +## & - allow string.punctuation only (doesn't include all unicode symbols) +## * - allow any visible character + ## Note: locale settings affect what "uppercase", lowercase, etc comprise. +## Note: '|' is not a maskchar, in that it is a mask processing directive, and so +## does not appear here. ## maskchars = ("#","A","a","X","C","N",'*','&') ansichars = "" @@ -1272,12 +1322,13 @@ class Field: 'validRequired': False, ## Set to True to disallow input that results in an invalid value 'emptyInvalid': False, ## Set to True to make EMPTY = INVALID 'description': "", ## primarily for autoformats, but could be useful elsewhere + 'raiseOnInvalidPaste': False, ## if True, paste into field will cause ValueError } # This list contains all parameters that when set at the control level should # propagate down to each field: propagating_params = ('fillChar', 'groupChar', 'decimalChar','useParensForNegatives', - 'compareNoCase', 'emptyInvalid', 'validRequired') + 'compareNoCase', 'emptyInvalid', 'validRequired', 'raiseOnInvalidPaste') def __init__(self, **kwargs): """ @@ -1289,7 +1340,9 @@ class Field: for key in kwargs.keys(): if key not in Field.valid_params.keys(): #### dbg(indent=0) - raise TypeError('invalid parameter "%s"' % (key)) + ae = AttributeError('invalid parameter "%s"' % (key)) + ae.attribute = key + raise ae # Set defaults for each parameter for this instance, and fully # populate initial parameter list for configuration: @@ -1316,7 +1369,9 @@ class Field: for key in kwargs.keys(): if key not in Field.valid_params.keys(): ## dbg(indent=0, suspend=0) - raise AttributeError('invalid keyword argument "%s"' % key) + ae = AttributeError('invalid keyword argument "%s"' % key) + ae.attribute = key + raise ae ## if self._index is not None: dbg('field index:', self._index) ## dbg('parameters:', indent=1) @@ -1384,7 +1439,9 @@ class Field: # Verify proper numeric format params: if self._groupdigits and self._groupChar == self._decimalChar: ## dbg(indent=0, suspend=0) - raise AttributeError("groupChar '%s' cannot be the same as decimalChar '%s'" % (self._groupChar, self._decimalChar)) + ae = AttributeError("groupChar '%s' cannot be the same as decimalChar '%s'" % (self._groupChar, self._decimalChar)) + ae.attribute = self._groupChar + raise ae # Now go do validation, semantic and inter-dependency parameter processing: @@ -1457,7 +1514,9 @@ class Field: continue if not self.IsValid(choice): ## dbg(indent=0, suspend=0) - raise ValueError('%s: "%s" is not a valid value for the control as specified.' % (str(self._index), choice)) + ve = ValueError('%s: "%s" is not a valid value for the control as specified.' % (str(self._index), choice)) + ve.value = choice + raise ve self._hasList = True #### dbg("kwargs.has_key('fillChar')?", kwargs.has_key('fillChar'), "len(self._choices) > 0?", len(self._choices) > 0) @@ -1742,7 +1801,7 @@ class MaskedEditMixin: 'C': string.letters, 'N': string.letters + string.digits, '&': string.punctuation, - '*': ansichars + '*': ansichars # to give it a value, but now allows any non-wxcontrol character } ## self._ignoreChange is used by MaskedComboBox, because @@ -1790,7 +1849,9 @@ class MaskedEditMixin: key = key.replace('Color', 'Colour') # for b-c, and standard wxPython spelling if key not in MaskedEditMixin.valid_ctrl_params.keys() + Field.valid_params.keys(): ## dbg(indent=0, suspend=0) - raise TypeError('Invalid keyword argument "%s" for control "%s"' % (key, self.name)) + ae = AttributeError('Invalid keyword argument "%s" for control "%s"' % (key, self.name)) + ae.attribute = key + raise ae elif key in Field.valid_params.keys(): constraint_kwargs[key] = value else: @@ -1818,7 +1879,9 @@ class MaskedEditMixin: constraint_kwargs[param] = value elif autoformat and not autoformat in masktags.keys(): - raise AttributeError('invalid value for autoformat parameter: %s' % repr(autoformat)) + ae = AttributeError('invalid value for autoformat parameter: %s' % repr(autoformat)) + ae.attribute = autoformat + raise ae else: ## dbg('autoformat not selected') if kwargs.has_key('mask'): @@ -1847,18 +1910,18 @@ class MaskedEditMixin: field = fields[i] if not isinstance(field, Field): ## dbg(indent=0, suspend=0) - raise AttributeError('invalid type for field parameter: %s' % repr(field)) + raise TypeError('invalid type for field parameter: %s' % repr(field)) self._fields[i] = field elif type(fields) == types.DictionaryType: for index, field in fields.items(): if not isinstance(field, Field): ## dbg(indent=0, suspend=0) - raise AttributeError('invalid type for field parameter: %s' % repr(field)) + raise TypeError('invalid type for field parameter: %s' % repr(field)) self._fields[index] = field else: ## dbg(indent=0, suspend=0) - raise AttributeError('fields parameter must be a list or dictionary; not %s' % repr(fields)) + raise TypeError('fields parameter must be a list or dictionary; not %s' % repr(fields)) # Assign constraint parameters for entire control: #### dbg('control constraints:', indent=1) @@ -2055,7 +2118,9 @@ class MaskedEditMixin: parameters.) """ if field_index not in self._field_indices: - raise IndexError('%s is not a valid field for control "%s".' % (str(field_index), self.name)) + ie = IndexError('%s is not a valid field for control "%s".' % (str(field_index), self.name)) + ie.index = field_index + raise ie # set parameters as requested: self._fields[field_index]._SetParameters(**kwargs) @@ -2096,11 +2161,15 @@ class MaskedEditMixin: Routine provided for getting a parameter of an individual field. """ if field_index not in self._field_indices: - raise IndexError('%s is not a valid field for control "%s".' % (str(field_index), self.name)) + ie = IndexError('%s is not a valid field for control "%s".' % (str(field_index), self.name)) + ie.index = field_index + raise ie elif Field.valid_params.has_key(paramname): return self._fields[field_index]._GetParameter(paramname) else: - TypeError('"%s".GetFieldParameter: invalid parameter "%s"' % (self.name, paramname)) + ae = AttributeError('"%s".GetFieldParameter: invalid parameter "%s"' % (self.name, paramname)) + ae.attribute = paramname + raise ae def _SetKeycodeHandler(self, keycode, func): @@ -2208,9 +2277,13 @@ class MaskedEditMixin: s += ' ' self._ctrl_constraints._defaultValue += ' ' + # Now, go build up a dictionary of booleans, indexed by position, - # indicating whether or not a given position is masked or not + # indicating whether or not a given position is masked or not. + # Also, strip out any '|' chars, adjusting the mask as necessary, + # marking the appropriate positions for field boundaries: ismasked = {} + explicit_field_boundaries = [] i = 0 while i < len(s): if s[i] == '\\': # if escaped character: @@ -2220,14 +2293,19 @@ class MaskedEditMixin: if i+2 < len(s) and s[i+1] == '\\': # if next char also a '\', char is a literal '\' s = s[:i] + s[i+1:] # elide the 2nd '\' as well + i += 1 # increment to next char + elif s[i] == '|': + s = s[:i] + s[i+1:] # elide the '|' + explicit_field_boundaries.append(i) + # keep index where it is: else: # else if special char, mark position accordingly ismasked[i] = s[i] in maskchars -#### dbg('ismasked[%d]:' % i, ismasked[i], s) - i += 1 # increment to next char +#### dbg('ismasked[%d]:' % i, ismasked[i], s) + i += 1 # increment to next char #### dbg('ismasked:', ismasked) ## dbg('new mask: "%s"' % s, indent=0) - return s, ismasked + return s, ismasked, explicit_field_boundaries def _calcFieldExtents(self): @@ -2311,6 +2389,8 @@ class MaskedEditMixin: while i < len(self._mask) and self._isMaskChar(i): self._lookupField[i] = field_index i += 1 + if i in self._explicit_field_boundaries: + break #### dbg('edit_end =', i) edit_end = i self._lookupField[i] = field_index @@ -2328,6 +2408,7 @@ class MaskedEditMixin: mask=self._mask[edit_start:edit_end]) pos = i i = self._findNextEntry(pos, adjustInsert=False) # go to next field: +#### dbg('next entry:', i) if i > pos: for j in range(pos, i+1): self._lookupField[j] = field_index @@ -2348,7 +2429,10 @@ class MaskedEditMixin: # Verify that all field indices specified are valid for mask: for index in self._fields.keys(): if index not in [-1] + self._lookupField.values(): - raise IndexError('field %d is not a valid field for mask "%s"' % (index, self._mask)) + ie = IndexError('field %d is not a valid field for mask "%s"' % (index, self._mask)) + ie.index = index + raise ie + def _calcTemplate(self, reset_fillchar, reset_default): @@ -2452,7 +2536,9 @@ class MaskedEditMixin: ## dbg('self._defaultValue:', self._defaultValue) if not self.IsEmpty(self._defaultValue) and not self.IsValid(self._defaultValue): #### dbg(indent=0) - raise ValueError('Default value of "%s" is not a valid value for control "%s"' % (self._defaultValue, self.name)) + ve = ValueError('Default value of "%s" is not a valid value for control "%s"' % (self._defaultValue, self.name)) + ve.value = self._defaultValue + raise ve # if no fillchar change, but old value == old template, replace it: if newvalue == old_template: @@ -2540,10 +2626,17 @@ class MaskedEditMixin: valid_paste, ignore, replace_to = self._validatePaste(choice, start, end) if not valid_paste: #### dbg(indent=0) - raise ValueError('"%s" could not be entered into field %d of control "%s"' % (choice, index, self.name)) + ve = ValueError('"%s" could not be entered into field %d of control "%s"' % (choice, index, self.name)) + ve.value = choice + ve.index = index + raise ve elif replace_to > end: #### dbg(indent=0) - raise ValueError('"%s" will not fit into field %d of control "%s"' (choice, index, self.name)) + ve = ValueError('"%s" will not fit into field %d of control "%s"' (choice, index, self.name)) + ve.value = choice + ve.index = index + raise ve + #### dbg(choice, 'valid in field', index) @@ -2568,7 +2661,7 @@ class MaskedEditMixin: # Preprocess specified mask to expand {n} syntax, handle escaped # mask characters, etc and build the resulting positionally keyed # dictionary for which positions are mask vs. template characters: - self._mask, self.ismasked = self._processMask(mask) + self._mask, self._ismasked, self._explicit_field_boundaries = self._processMask(mask) self._masklength = len(self._mask) #### dbg('processed mask:', self._mask) @@ -2883,13 +2976,6 @@ class MaskedEditMixin: ## dbg("field length exceeded:",pos) keep_processing = False - if keep_processing: - if self._isMaskChar(pos): ## Get string of allowed characters for validation - okchars = self._getAllowedChars(pos) - else: -## dbg('Not a valid position: pos = ', pos,"chars=",maskchars) - okchars = "" - key = self._adjustKey(pos, key) # apply formatting constraints to key: if self._keyhandlers.has_key(key): @@ -2902,30 +2988,35 @@ class MaskedEditMixin: ## dbg(indent=0) return # else skip default processing, but do final formatting - if key < wx.WXK_SPACE or key > 255: -## dbg('key < WXK_SPACE or key > 255') - event.Skip() # non alphanumeric + if key in wx_control_keycodes: +## dbg('key in wx_control_keycodes') + event.Skip() # non-printable; let base control handle it keep_processing = False else: field = self._FindField(pos) -## dbg("key ='%s'" % chr(key)) - if chr(key) == ' ': -## dbg('okSpaces?', field._okSpaces) - pass - - char = chr(key) # (must work if we got this far) - if 'unicode' in wx.PlatformInfo: - char = char.decode(self._defaultEncoding) + if key < 256: + char = chr(key) # (must work if we got this far) + char = char.decode(self._defaultEncoding) + else: + char = unichr(event.GetUnicodeKey()) + dbg('unicode char:', char) excludes = u'' if type(field._excludeChars) != types.UnicodeType: excludes += field._excludeChars.decode(self._defaultEncoding) if type(self._ctrl_constraints) != types.UnicodeType: excludes += self._ctrl_constraints._excludeChars.decode(self._defaultEncoding) else: + char = chr(key) # (must work if we got this far) excludes = field._excludeChars + self._ctrl_constraints._excludeChars +## dbg("key ='%s'" % chr(key)) + if chr(key) == ' ': +## dbg('okSpaces?', field._okSpaces) + pass + + if char in excludes: keep_processing = False @@ -3910,7 +4001,8 @@ class MaskedEditMixin: def _findNextEntry(self,pos, adjustInsert=True): """ Find the insertion point for the next valid entry character position.""" - if self._isTemplateChar(pos): # if changing fields, pay attn to flag +## dbg('MaskedEditMixin::_findNextEntry', indent=1) + if self._isTemplateChar(pos) or pos in self._explicit_field_boundaries: # if changing fields, pay attn to flag adjustInsert = adjustInsert else: # else within a field; flag not relevant adjustInsert = False @@ -3927,6 +4019,7 @@ class MaskedEditMixin: slice = self._GetValue()[start:end] if field._insertRight and field.IsEmpty(slice): pos = end +## dbg('final pos:', pos, indent=0) return pos @@ -4447,7 +4540,7 @@ class MaskedEditMixin: """ Returns True if the char at position pos is a special mask character (e.g. NCXaA#) """ if pos < self._masklength: - return self.ismasked[pos] + return self._ismasked[pos] else: return False @@ -4533,7 +4626,7 @@ class MaskedEditMixin: okChars += ')' #### dbg('%s in %s?' % (char, okChars), char in okChars) - approved = char in okChars + approved = (self.maskdict[pos] == '*' or char in okChars) if approved and checkRegex: ## dbg("checking appropriate regex's") @@ -5558,9 +5651,13 @@ class MaskedEditMixin: if raise_on_invalid: ## dbg(indent=0, suspend=0) if item == 'control': - raise ValueError('"%s" will not fit into the control "%s"' % (paste_text, self.name)) + ve = ValueError('"%s" will not fit into the control "%s"' % (paste_text, self.name)) + ve.value = paste_text + raise ve else: - raise ValueError('"%s" will not fit into the selection' % paste_text) + ve = ValueError('"%s" will not fit into the selection' % paste_text) + ve.value = paste_text + raise ve else: ## dbg(indent=0, suspend=0) return False, None, None @@ -5615,13 +5712,18 @@ class MaskedEditMixin: if not valid_paste and raise_on_invalid: ## dbg('raising exception', indent=0, suspend=0) - raise ValueError('"%s" cannot be inserted into the control "%s"' % (paste_text, self.name)) + ve = ValueError('"%s" cannot be inserted into the control "%s"' % (paste_text, self.name)) + ve.value = paste_text + raise ve + elif i < len(paste_text): valid_paste = False if raise_on_invalid: ## dbg('raising exception', indent=0, suspend=0) - raise ValueError('"%s" will not fit into the control "%s"' % (paste_text, self.name)) + ve = ValueError('"%s" will not fit into the control "%s"' % (paste_text, self.name)) + ve.value = paste_text + raise ve ## dbg('valid_paste?', valid_paste) if valid_paste: @@ -5735,6 +5837,7 @@ class MaskedEditMixin: sel_start = 0 ## dbg('adjusted selection:', (sel_start, sel_to)) + raise_on_invalid = raise_on_invalid or field._raiseOnInvalidPaste try: valid_paste, replacement_text, replace_to = self._validatePaste(paste_text, sel_start, sel_to, raise_on_invalid) except: @@ -6599,6 +6702,19 @@ __i=0 ## CHANGELOG: ## ==================== +## Version 1.11 +## 1. Added value member to ValueError exceptions, so that people can catch them +## and then display their own errors, and added attribute raiseOnInvalidPaste, +## so one doesn't have to subclass the controls simply to force generation of +## a ValueError on a bad paste operation. +## 2. Fixed handling of unicode charsets by converting to explicit control char +## set testing for passing those keystrokes to the base control, and then +## changing the semantics of the * maskchar to indicate any visible char. +## 3. Added '|' mask specification character, which allows splitting of contiguous +## mask characters into separate fields, allowing finer control of behavior +## of a control. +## +## ## Version 1.10 ## 1. Added handling for WXK_DELETE and WXK_INSERT, such that shift-delete ## cuts, shift-insert pastes, and ctrl-insert copies. -- 2.47.2