|
|
|
@@ -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,6 +2293,11 @@ 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)
|
|
|
|
@@ -2227,7 +2305,7 @@ class MaskedEditMixin:
|
|
|
|
|
#### 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:
|
|
|
|
|
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.
|
|
|
|
|