home *** CD-ROM | disk | FTP | other *** search
Wrap
# Source Generated with Decompyle++ # File: in.pyo (Python 2.3) from wxPython.wx import * import string import re import copy import difflib import types from wxPython.tools.dbg import Logger dbg = Logger() dbg(enable = 0) WXK_CTRL_A = ord('A') + 1 - ord('A') WXK_CTRL_C = ord('C') + 1 - ord('A') WXK_CTRL_S = ord('S') + 1 - ord('A') WXK_CTRL_V = ord('V') + 1 - ord('A') WXK_CTRL_X = ord('X') + 1 - ord('A') WXK_CTRL_Z = ord('Z') + 1 - ord('A') nav = (WXK_BACK, WXK_LEFT, WXK_RIGHT, WXK_UP, WXK_DOWN, WXK_TAB, WXK_HOME, WXK_END, WXK_RETURN, WXK_PRIOR, WXK_NEXT) control = (WXK_BACK, WXK_DELETE, WXK_CTRL_A, WXK_CTRL_C, WXK_CTRL_S, WXK_CTRL_V, WXK_CTRL_X, WXK_CTRL_Z) maskchars = ('#', 'A', 'a', 'X', 'C', 'N', '&') months = '(01|02|03|04|05|06|07|08|09|10|11|12)' 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)' charmonths_dict = { 'jan': 1, 'feb': 2, 'mar': 3, 'apr': 4, 'may': 5, 'jun': 6, 'jul': 7, 'aug': 8, 'sep': 9, 'oct': 10, 'nov': 11, 'dec': 12 } 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)' hours = '(0\\d| \\d|1[012])' 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)' minutes = '(00|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|32|33|34|35|36|37|38|39|40|41|42|43|44|45|46|47|48|49|50|51|52|53|54|55|56|57|58|59)' seconds = minutes 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' 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(',') state_names = [ 'Alabama', 'Alaska', 'Arizona', 'Arkansas', 'California', 'Colorado', 'Connecticut', 'Delaware', 'District of Columbia', 'Florida', 'Georgia', 'Hawaii', 'Idaho', 'Illinois', 'Indiana', 'Iowa', 'Kansas', 'Kentucky', 'Louisiana', 'Maine', 'Maryland', 'Massachusetts', 'Michigan', 'Minnesota', 'Mississippi', 'Missouri', 'Montana', 'Nebraska', 'Nevada', 'New Hampshire', 'New Jersey', 'New Mexico', 'New York', 'North Carolina', 'North Dakokta', 'Ohio', 'Oklahoma', 'Oregon', 'Pennsylvania', 'Puerto Rico', 'Rhode Island', 'South Carolina', 'South Dakota', 'Tennessee', 'Texas', 'Utah', 'Vermont', 'Virginia', 'Washington', 'West Virginia', 'Wisconsin', 'Wyoming'] masktags = { 'USPHONEFULLEXT': { 'mask': '(###) ###-#### x:###', 'formatcodes': 'F^->', 'validRegex': '^\\(\\d{3}\\) \\d{3}-\\d{4}', 'description': 'Phone Number w/opt. ext' }, 'USPHONETIGHTEXT': { 'mask': '###-###-#### x:###', 'formatcodes': 'F^->', 'validRegex': '^\\d{3}-\\d{3}-\\d{4}', 'description': 'Phone Number\n (w/hyphens and opt. ext)' }, 'USPHONEFULL': { 'mask': '(###) ###-####', 'formatcodes': 'F^->', 'validRegex': '^\\(\\d{3}\\) \\d{3}-\\d{4}', 'description': 'Phone Number only' }, 'USPHONETIGHT': { 'mask': '###-###-####', 'formatcodes': 'F^->', 'validRegex': '^\\d{3}-\\d{3}-\\d{4}', 'description': 'Phone Number\n(w/hyphens)' }, 'USSTATE': { 'mask': 'AA', 'formatcodes': 'F!V', 'validRegex': '([ACDFGHIKLMNOPRSTUVW] |%s)' % string.join(states, '|'), 'choices': states, 'choiceRequired': True, 'description': 'US State Code' }, 'USSTATENAME': { 'mask': 'ACCCCCCCCCCCCCCCCCCC', 'formatcodes': 'F_', 'validRegex': '([ACDFGHIKLMNOPRSTUVW] |%s)' % string.join(state_names, '|'), 'choices': state_names, 'choiceRequired': True, 'description': 'US State Name' }, 'USDATETIMEMMDDYYYY/HHMMSS': { 'mask': '##/##/#### ##:##:## AM', 'excludeChars': am_pm_exclude, 'formatcodes': 'DF!', 'validRegex': '^' + months + '/' + days + '/' + '\\d{4} ' + hours + ':' + minutes + ':' + seconds + ' (A|P)M', 'description': 'US Date + Time' }, 'USDATETIMEMMDDYYYY-HHMMSS': { 'mask': '##-##-#### ##:##:## AM', 'excludeChars': am_pm_exclude, 'formatcodes': 'DF!', 'validRegex': '^' + months + '-' + days + '-' + '\\d{4} ' + hours + ':' + minutes + ':' + seconds + ' (A|P)M', 'description': 'US Date + Time\n(w/hypens)' }, 'USDATEMILTIMEMMDDYYYY/HHMMSS': { 'mask': '##/##/#### ##:##:##', 'formatcodes': 'DF', 'validRegex': '^' + months + '/' + days + '/' + '\\d{4} ' + milhours + ':' + minutes + ':' + seconds, 'description': 'US Date + Military Time' }, 'USDATEMILTIMEMMDDYYYY-HHMMSS': { 'mask': '##-##-#### ##:##:##', 'formatcodes': 'DF', 'validRegex': '^' + months + '-' + days + '-' + '\\d{4} ' + milhours + ':' + minutes + ':' + seconds, 'description': 'US Date + Military Time\n(w/hypens)' }, 'USDATETIMEMMDDYYYY/HHMM': { 'mask': '##/##/#### ##:## AM', 'excludeChars': am_pm_exclude, 'formatcodes': 'DF!', 'validRegex': '^' + months + '/' + days + '/' + '\\d{4} ' + hours + ':' + minutes + ' (A|P)M', 'description': 'US Date + Time\n(without seconds)' }, 'USDATEMILTIMEMMDDYYYY/HHMM': { 'mask': '##/##/#### ##:##', 'formatcodes': 'DF', 'validRegex': '^' + months + '/' + days + '/' + '\\d{4} ' + milhours + ':' + minutes, 'description': 'US Date + Military Time\n(without seconds)' }, 'USDATETIMEMMDDYYYY-HHMM': { 'mask': '##-##-#### ##:## AM', 'excludeChars': am_pm_exclude, 'formatcodes': 'DF!', 'validRegex': '^' + months + '-' + days + '-' + '\\d{4} ' + hours + ':' + minutes + ' (A|P)M', 'description': 'US Date + Time\n(w/hypens and w/o secs)' }, 'USDATEMILTIMEMMDDYYYY-HHMM': { 'mask': '##-##-#### ##:##', 'formatcodes': 'DF', 'validRegex': '^' + months + '-' + days + '-' + '\\d{4} ' + milhours + ':' + minutes, 'description': 'US Date + Military Time\n(w/hyphens and w/o seconds)' }, 'USDATEMMDDYYYY/': { 'mask': '##/##/####', 'formatcodes': 'DF', 'validRegex': '^' + months + '/' + days + '/' + '\\d{4}', 'description': 'US Date\n(MMDDYYYY)' }, 'USDATEMMDDYY/': { 'mask': '##/##/##', 'formatcodes': 'DF', 'validRegex': '^' + months + '/' + days + '/\\d\\d', 'description': 'US Date\n(MMDDYY)' }, 'USDATEMMDDYYYY-': { 'mask': '##-##-####', 'formatcodes': 'DF', 'validRegex': '^' + months + '-' + days + '-' + '\\d{4}', 'description': 'MM-DD-YYYY' }, 'EUDATEYYYYMMDD/': { 'mask': '####/##/##', 'formatcodes': 'DF', 'validRegex': '^' + '\\d{4}' + '/' + months + '/' + days, 'description': 'YYYY/MM/DD' }, 'EUDATEYYYYMMDD.': { 'mask': '####.##.##', 'formatcodes': 'DF', 'validRegex': '^' + '\\d{4}' + '.' + months + '.' + days, 'description': 'YYYY.MM.DD' }, 'EUDATEDDMMYYYY/': { 'mask': '##/##/####', 'formatcodes': 'DF', 'validRegex': '^' + days + '/' + months + '/' + '\\d{4}', 'description': 'DD/MM/YYYY' }, 'EUDATEDDMMYYYY.': { 'mask': '##.##.####', 'formatcodes': 'DF', 'validRegex': '^' + days + '.' + months + '.' + '\\d{4}', 'description': 'DD.MM.YYYY' }, 'EUDATEDDMMMYYYY.': { 'mask': '##.CCC.####', 'formatcodes': 'DF', 'validRegex': '^' + days + '.' + charmonths + '.' + '\\d{4}', 'description': 'DD.Month.YYYY' }, 'EUDATEDDMMMYYYY/': { 'mask': '##/CCC/####', 'formatcodes': 'DF', 'validRegex': '^' + days + '/' + charmonths + '/' + '\\d{4}', 'description': 'DD/Month/YYYY' }, 'EUDATETIMEYYYYMMDD/HHMMSS': { 'mask': '####/##/## ##:##:## AM', 'excludeChars': am_pm_exclude, 'formatcodes': 'DF!', 'validRegex': '^' + '\\d{4}' + '/' + months + '/' + days + ' ' + hours + ':' + minutes + ':' + seconds + ' (A|P)M', 'description': 'YYYY/MM/DD HH:MM:SS' }, 'EUDATETIMEYYYYMMDD.HHMMSS': { 'mask': '####.##.## ##:##:## AM', 'excludeChars': am_pm_exclude, 'formatcodes': 'DF!', 'validRegex': '^' + '\\d{4}' + '.' + months + '.' + days + ' ' + hours + ':' + minutes + ':' + seconds + ' (A|P)M', 'description': 'YYYY.MM.DD HH:MM:SS' }, 'EUDATETIMEDDMMYYYY/HHMMSS': { 'mask': '##/##/#### ##:##:## AM', 'excludeChars': am_pm_exclude, 'formatcodes': 'DF!', 'validRegex': '^' + days + '/' + months + '/' + '\\d{4} ' + hours + ':' + minutes + ':' + seconds + ' (A|P)M', 'description': 'DD/MM/YYYY HH:MM:SS' }, 'EUDATETIMEDDMMYYYY.HHMMSS': { 'mask': '##.##.#### ##:##:## AM', 'excludeChars': am_pm_exclude, 'formatcodes': 'DF!', 'validRegex': '^' + days + '.' + months + '.' + '\\d{4} ' + hours + ':' + minutes + ':' + seconds + ' (A|P)M', 'description': 'DD.MM.YYYY HH:MM:SS' }, 'EUDATETIMEYYYYMMDD/HHMM': { 'mask': '####/##/## ##:## AM', 'excludeChars': am_pm_exclude, 'formatcodes': 'DF!', 'validRegex': '^' + '\\d{4}' + '/' + months + '/' + days + ' ' + hours + ':' + minutes + ' (A|P)M', 'description': 'YYYY/MM/DD HH:MM' }, 'EUDATETIMEYYYYMMDD.HHMM': { 'mask': '####.##.## ##:## AM', 'excludeChars': am_pm_exclude, 'formatcodes': 'DF!', 'validRegex': '^' + '\\d{4}' + '.' + months + '.' + days + ' ' + hours + ':' + minutes + ' (A|P)M', 'description': 'YYYY.MM.DD HH:MM' }, 'EUDATETIMEDDMMYYYY/HHMM': { 'mask': '##/##/#### ##:## AM', 'excludeChars': am_pm_exclude, 'formatcodes': 'DF!', 'validRegex': '^' + days + '/' + months + '/' + '\\d{4} ' + hours + ':' + minutes + ' (A|P)M', 'description': 'DD/MM/YYYY HH:MM' }, 'EUDATETIMEDDMMYYYY.HHMM': { 'mask': '##.##.#### ##:## AM', 'excludeChars': am_pm_exclude, 'formatcodes': 'DF!', 'validRegex': '^' + days + '.' + months + '.' + '\\d{4} ' + hours + ':' + minutes + ' (A|P)M', 'description': 'DD.MM.YYYY HH:MM' }, 'EUDATEMILTIMEYYYYMMDD/HHMMSS': { 'mask': '####/##/## ##:##:##', 'formatcodes': 'DF', 'validRegex': '^' + '\\d{4}' + '/' + months + '/' + days + ' ' + milhours + ':' + minutes + ':' + seconds, 'description': 'YYYY/MM/DD Mil. Time' }, 'EUDATEMILTIMEYYYYMMDD.HHMMSS': { 'mask': '####.##.## ##:##:##', 'formatcodes': 'DF', 'validRegex': '^' + '\\d{4}' + '.' + months + '.' + days + ' ' + milhours + ':' + minutes + ':' + seconds, 'description': 'YYYY.MM.DD Mil. Time' }, 'EUDATEMILTIMEDDMMYYYY/HHMMSS': { 'mask': '##/##/#### ##:##:##', 'formatcodes': 'DF', 'validRegex': '^' + days + '/' + months + '/' + '\\d{4} ' + milhours + ':' + minutes + ':' + seconds, 'description': 'DD/MM/YYYY Mil. Time' }, 'EUDATEMILTIMEDDMMYYYY.HHMMSS': { 'mask': '##.##.#### ##:##:##', 'formatcodes': 'DF', 'validRegex': '^' + days + '.' + months + '.' + '\\d{4} ' + milhours + ':' + minutes + ':' + seconds, 'description': 'DD.MM.YYYY Mil. Time' }, 'EUDATEMILTIMEYYYYMMDD/HHMM': { 'mask': '####/##/## ##:##', 'formatcodes': 'DF', 'validRegex': '^' + '\\d{4}' + '/' + months + '/' + days + ' ' + milhours + ':' + minutes, 'description': 'YYYY/MM/DD Mil. Time\n(w/o seconds)' }, 'EUDATEMILTIMEYYYYMMDD.HHMM': { 'mask': '####.##.## ##:##', 'formatcodes': 'DF', 'validRegex': '^' + '\\d{4}' + '.' + months + '.' + days + ' ' + milhours + ':' + minutes, 'description': 'YYYY.MM.DD Mil. Time\n(w/o seconds)' }, 'EUDATEMILTIMEDDMMYYYY/HHMM': { 'mask': '##/##/#### ##:##', 'formatcodes': 'DF', 'validRegex': '^' + days + '/' + months + '/' + '\\d{4} ' + milhours + ':' + minutes, 'description': 'DD/MM/YYYY Mil. Time\n(w/o seconds)' }, 'EUDATEMILTIMEDDMMYYYY.HHMM': { 'mask': '##.##.#### ##:##', 'formatcodes': 'DF', 'validRegex': '^' + days + '.' + months + '.' + '\\d{4} ' + milhours + ':' + minutes, 'description': 'DD.MM.YYYY Mil. Time\n(w/o seconds)' }, 'TIMEHHMMSS': { 'mask': '##:##:## AM', 'excludeChars': am_pm_exclude, 'formatcodes': 'TF!', 'validRegex': '^' + hours + ':' + minutes + ':' + seconds + ' (A|P)M', 'description': 'HH:MM:SS (A|P)M\n(see wxTimeCtrl)' }, 'TIMEHHMM': { 'mask': '##:## AM', 'excludeChars': am_pm_exclude, 'formatcodes': 'TF!', 'validRegex': '^' + hours + ':' + minutes + ' (A|P)M', 'description': 'HH:MM (A|P)M\n(see wxTimeCtrl)' }, 'MILTIMEHHMMSS': { 'mask': '##:##:##', 'formatcodes': 'TF', 'validRegex': '^' + milhours + ':' + minutes + ':' + seconds, 'description': 'Military HH:MM:SS\n(see wxTimeCtrl)' }, 'MILTIMEHHMM': { 'mask': '##:##', 'formatcodes': 'TF', 'validRegex': '^' + milhours + ':' + minutes, 'description': 'Military HH:MM\n(see wxTimeCtrl)' }, 'USSOCIALSEC': { 'mask': '###-##-####', 'formatcodes': 'F', 'validRegex': '\\d{3}-\\d{2}-\\d{4}', 'description': 'Social Sec#' }, 'CREDITCARD': { 'mask': '####-####-####-####', 'formatcodes': 'F', 'validRegex': '\\d{4}-\\d{4}-\\d{4}-\\d{4}', 'description': 'Credit Card' }, 'EXPDATEMMYY': { 'mask': '##/##', 'formatcodes': 'F', 'validRegex': '^' + months + '/\\d\\d', 'description': 'Expiration MM/YY' }, 'USZIP': { 'mask': '#####', 'formatcodes': 'F', 'validRegex': '^\\d{5}', 'description': 'US 5-digit zip code' }, 'USZIPPLUS4': { 'mask': '#####-####', 'formatcodes': 'F', 'validRegex': '\\d{5}-(\\s{4}|\\d{4})', 'description': 'US zip+4 code' }, 'PERCENT': { 'mask': '0.##', 'formatcodes': 'F', 'validRegex': '^0.\\d\\d', 'description': 'Percentage' }, 'AGE': { 'mask': '###', 'formatcodes': 'F', 'validRegex': '^[1-9]{1} |[1-9][0-9] |1[0|1|2][0-9]', 'description': 'Age' }, 'EMAIL': { 'mask': 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX', 'excludeChars': ' \\/*&%$#!+=\'"', 'formatcodes': 'F>', 'validRegex': '^\\w+([\\-\\.]\\w+)*@((([a-zA-Z0-9]+(\\-[a-zA-Z0-9]+)*\\.)+)[a-zA-Z]{2,4}|\\[(\\d|\\d\\d|(1\\d\\d|2[0-4]\\d|25[0-5]))(\\.(\\d|\\d\\d|(1\\d\\d|2[0-4]\\d|25[0-5]))){3}\\]) *$', 'description': 'Email address' }, 'IPADDR': { 'mask': '###.###.###.###', 'formatcodes': 'F_Sr', '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}', 'description': 'IP Address\n(see wxIpAddrCtrl)' } } autoformats = [] for key, value in masktags.items(): autoformats.append((key, value['description'])) autoformats.sort() class Field: valid_params = { 'index': None, 'mask': '', 'extent': (), 'formatcodes': '', 'fillChar': ' ', 'groupChar': ',', 'decimalChar': '.', 'shiftDecimalChar': '>', 'useParensForNegatives': False, 'defaultValue': '', 'excludeChars': '', 'includeChars': '', 'validRegex': '', 'validRange': (), 'choices': [], 'choiceRequired': False, 'compareNoCase': False, 'autoSelect': False, 'validFunc': None, 'validRequired': False, 'emptyInvalid': False, 'description': '' } propagating_params = ('fillChar', 'groupChar', 'decimalChar', 'useParensForNegatives', 'compareNoCase', 'emptyInvalid', 'validRequired') def __init__(self, **kwargs): for key in kwargs.keys(): if key not in Field.valid_params.keys(): raise TypeError('invalid parameter "%s"' % key) continue for key, value in Field.valid_params.items(): setattr(self, '_' + key, copy.copy(value)) if not kwargs.has_key(key): kwargs[key] = copy.copy(value) continue self._autoCompleteIndex = -1 self._SetParameters(**kwargs) self._ValidateParameters(**kwargs) def _SetParameters(self, **kwargs): dbg(suspend = 1) dbg('maskededit.Field::_SetParameters', indent = 1) 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) continue if self._index is not None: dbg('field index:', self._index) dbg('parameters:', indent = 1) for key, value in kwargs.items(): dbg('%s:' % key, value) dbg(indent = 0) old_fillChar = self._fillChar for key in Field.valid_params.keys(): if kwargs.has_key(key): setattr(self, '_' + key, kwargs[key]) continue if kwargs.has_key('formatcodes'): self._forceupper = '!' in self._formatcodes self._forcelower = '^' in self._formatcodes self._groupdigits = ',' in self._formatcodes self._okSpaces = '_' in self._formatcodes self._padZero = '0' in self._formatcodes self._autofit = 'F' in self._formatcodes self._insertRight = 'r' in self._formatcodes self._allowInsert = '>' in self._formatcodes if not 'R' in self._formatcodes: pass self._alignRight = 'r' in self._formatcodes self._moveOnFieldFull = not ('<' in self._formatcodes) self._selectOnFieldEntry = 'S' in self._formatcodes if kwargs.has_key('groupChar'): self._groupChar = kwargs['groupChar'] if kwargs.has_key('decimalChar'): self._decimalChar = kwargs['decimalChar'] if kwargs.has_key('shiftDecimalChar'): self._shiftDecimalChar = kwargs['shiftDecimalChar'] if kwargs.has_key('formatcodes') or kwargs.has_key('validRegex'): if 'V' in self._formatcodes: pass self._regexMask = self._validRegex if kwargs.has_key('fillChar'): self._old_fillChar = old_fillChar if kwargs.has_key('mask') or kwargs.has_key('validRegex'): self._isInt = isInteger(self._mask) dbg('isInt?', self._isInt, 'self._mask:"%s"' % self._mask) dbg(indent = 0, suspend = 0) def _ValidateParameters(self, **kwargs): dbg(suspend = 1) dbg('maskededit.Field::_ValidateParameters', indent = 1) if self._index is not None: dbg('field index:', self._index) 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)) if kwargs.has_key('validRegex'): if self._validRegex: try: if self._compareNoCase: self._filter = re.compile(self._validRegex, re.IGNORECASE) else: self._filter = re.compile(self._validRegex) dbg(indent = 0, suspend = 0) raise TypeError('%s: validRegex "%s" not a legal regular expression' % (str(self._index), self._validRegex)) else: self._filter = None if kwargs.has_key('validRange'): self._hasRange = False self._rangeHigh = 0 self._rangeLow = 0 if self._validRange: if type(self._validRange) != types.TupleType and len(self._validRange) != 2 or self._validRange[0] > self._validRange[1]: dbg(indent = 0, suspend = 0) raise TypeError('%s: validRange %s parameter must be tuple of form (a,b) where a <= b' % (str(self._index), repr(self._validRange))) self._hasRange = True self._rangeLow = self._validRange[0] self._rangeHigh = self._validRange[1] if kwargs.has_key('choices') and len(self._choices) and len(self._choices[0]) != len(self._mask): self._hasList = False if self._choices and type(self._choices) not in (types.TupleType, types.ListType): dbg(indent = 0, suspend = 0) raise TypeError('%s: choices must be a sequence of strings' % str(self._index)) elif len(self._choices) > 0: for choice in self._choices: if type(choice) not in (types.StringType, types.UnicodeType): dbg(indent = 0, suspend = 0) raise TypeError('%s: choices must be a sequence of strings' % str(self._index)) continue length = len(self._mask) dbg('len(%s)' % self._mask, length, 'len(self._choices):', len(self._choices), 'length:', length, 'self._alignRight?', self._alignRight) if hasattr(self, '_template'): for choice in self._choices: if self.IsEmpty(choice) and not (self._validRequired): 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)) continue self._hasList = True if kwargs.has_key('autoSelect') and kwargs['autoSelect']: if not (self._hasList): dbg('no list to auto complete; ignoring "autoSelect=True"') self._autoSelect = False self._valid = True dbg(indent = 0, suspend = 0) def _GetParameter(self, paramname): if Field.valid_params.has_key(paramname): return getattr(self, '_' + paramname) else: TypeError('Field._GetParameter: invalid parameter "%s"' % key) def IsEmpty(self, slice): dbg('Field::IsEmpty("%s")' % slice, indent = 1) if not hasattr(self, '_template'): dbg(indent = 0) raise AttributeError('_template') dbg('self._template: "%s"' % self._template) dbg('self._defaultValue: "%s"' % str(self._defaultValue)) if slice == self._template and not (self._defaultValue): dbg(indent = 0) return True elif slice == self._template: empty = True for pos in range(len(self._template)): if slice[pos] not in (' ', self._fillChar): empty = False break continue dbg('IsEmpty? %(empty)d (do all mask chars == fillChar?)' % locals(), indent = 0) return empty else: dbg("IsEmpty? 0 (slice doesn't match template)", indent = 0) return False def IsValid(self, slice): dbg(suspend = 1) dbg('Field[%s]::IsValid("%s")' % (str(self._index), slice), indent = 1) valid = True if self.IsEmpty(slice): dbg(indent = 0, suspend = 0) if self._emptyInvalid: return False else: return True elif self._hasList and self._choiceRequired: dbg('(member of list required)') if self._fillChar != ' ': slice = slice.replace(self._fillChar, ' ') dbg('updated slice:"%s"' % slice) compareStr = slice.strip() if self._compareNoCase: compareStr = compareStr.lower() valid = compareStr in self._compareChoices elif self._hasRange and not self.IsEmpty(slice): dbg('validating against range') try: valid = None if float(slice) <= float(slice) else float(slice) <= self._rangeHigh valid = False elif self._validRegex and self._filter: dbg('validating against regex') valid = re.match(self._filter, slice) is not None if valid and self._validFunc: dbg('validating against supplied function') valid = self._validFunc(slice) dbg('valid?', valid, indent = 0, suspend = 0) return valid def _AdjustField(self, slice): dbg('Field::_AdjustField("%s")' % slice, indent = 1) length = len(self._mask) if self._isInt: if self._useParensForNegatives: signpos = slice.find('(') right_signpos = slice.find(')') intStr = slice.replace('(', '').replace(')', '') else: signpos = slice.find('-') intStr = slice.replace('-', '') right_signpos = -1 intStr = intStr.replace(' ', '') intStr = string.replace(intStr, self._fillChar, '') intStr = string.replace(intStr, '-', '') intStr = string.replace(intStr, self._groupChar, '') (start, end) = self._extent field_len = end - start if not (self._padZero) and len(intStr) != field_len and intStr.strip(): intStr = str(long(intStr)) if self._groupdigits: new = '' cnt = 1 for i in range(len(intStr) - 1, -1, -1): new = intStr[i] + new if cnt % 3 == 0: new = self._groupChar + new cnt += 1 if new and new[0] == self._groupChar: new = new[1:] if len(new) <= length: intStr = new dbg('padzero?', self._padZero) dbg('len(intStr):', len(intStr), 'field length:', length) if self._padZero and len(intStr) < length: intStr = '0' * (length - len(intStr)) + intStr if signpos != -1: if self._useParensForNegatives: intStr = '(' + intStr[1:] if right_signpos != -1: intStr += ')' else: intStr = '-' + intStr[1:] elif signpos != -1 and slice[0:signpos].strip() == '': if self._useParensForNegatives: intStr = '(' + intStr if right_signpos != -1: intStr += ')' else: intStr = '-' + intStr elif right_signpos != -1: intStr += ')' slice = intStr slice = slice.strip() if self._alignRight: slice = slice.rjust(length) else: slice = slice.ljust(length) if self._fillChar != ' ': slice = slice.replace(' ', self._fillChar) dbg('adjusted slice: "%s"' % slice, indent = 0) return slice class wxMaskedEditMixin: valid_ctrl_params = { 'mask': 'XXXXXXXXXXXXX', 'autoformat': '', 'fields': { }, 'datestyle': 'MDY', 'autoCompleteKeycodes': [], 'useFixedWidthFont': True, 'retainFieldValidation': False, 'emptyBackgroundColour': 'White', 'validBackgroundColour': 'White', 'invalidBackgroundColour': 'Yellow', 'foregroundColour': 'Black', 'signedForegroundColour': 'Red', 'demo': False } def __init__(self, name = 'wxMaskedEdit', **kwargs): self.name = name if not hasattr(self, 'controlInitialized'): self.controlInitialized = False self.modified = False self._previous_mask = None for key in kwargs.keys(): if key.replace('Color', 'Colour') not in wxMaskedEditMixin.valid_ctrl_params.keys() + Field.valid_params.keys(): raise TypeError('%s: invalid parameter "%s"' % (name, key)) continue self._keyhandlers = { WXK_BACK: self._OnErase, WXK_LEFT: self._OnArrow, WXK_RIGHT: self._OnArrow, WXK_UP: self._OnAutoCompleteField, WXK_DOWN: self._OnAutoCompleteField, WXK_TAB: self._OnChangeField, WXK_HOME: self._OnHome, WXK_END: self._OnEnd, WXK_RETURN: self._OnReturn, WXK_PRIOR: self._OnAutoCompleteField, WXK_NEXT: self._OnAutoCompleteField, WXK_DELETE: self._OnErase, WXK_CTRL_A: self._OnCtrl_A, WXK_CTRL_C: self._OnCtrl_C, WXK_CTRL_S: self._OnCtrl_S, WXK_CTRL_V: self._OnCtrl_V, WXK_CTRL_X: self._OnCtrl_X, WXK_CTRL_Z: self._OnCtrl_Z } self._nav = list(nav) self._control = list(control) self.maskchardict = { '#': string.digits, 'A': string.uppercase, 'a': string.lowercase, 'X': string.letters + string.punctuation + string.digits, 'C': string.letters, 'N': string.letters + string.digits, '&': string.punctuation } self._ignoreChange = False self._curValue = None self._prevValue = None self._valid = True for key, value in wxMaskedEditMixin.valid_ctrl_params.items(): setattr(self, '_' + key, copy.copy(value)) if not kwargs.has_key(key): kwargs[key] = copy.copy(value) continue self._ctrl_constraints = self._fields[-1] = Field(index = -1) self.SetCtrlParameters(**kwargs) def SetCtrlParameters(self, **kwargs): dbg(suspend = 1) dbg('wxMaskedEditMixin::SetCtrlParameters', indent = 1) constraint_kwargs = { } ctrl_kwargs = { } for key, value in kwargs.items(): key = key.replace('Color', 'Colour') if key not in wxMaskedEditMixin.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)) continue if key in Field.valid_params.keys(): constraint_kwargs[key] = value continue ctrl_kwargs[key] = value mask = None reset_args = { } if ctrl_kwargs.has_key('autoformat'): autoformat = ctrl_kwargs['autoformat'] else: autoformat = None if autoformat != self._autoformat and autoformat in masktags.keys(): dbg('autoformat:', autoformat) self._autoformat = autoformat mask = masktags[self._autoformat]['mask'] for param, value in masktags[self._autoformat].items(): if param == 'mask': continue constraint_kwargs[param] = value elif autoformat and not (autoformat in masktags.keys()): raise AttributeError('invalid value for autoformat parameter: %s' % repr(autoformat)) else: dbg('autoformat not selected') if kwargs.has_key('mask'): mask = kwargs['mask'] dbg('mask:', mask) if mask is None: dbg('preserving previous mask') mask = self._previous_mask else: dbg('mask (re)set') reset_args['reset_mask'] = mask constraint_kwargs['mask'] = mask self._fields = { -1: self._ctrl_constraints } if ctrl_kwargs.has_key('fields'): fields = ctrl_kwargs['fields'] if type(fields) in (types.ListType, types.TupleType): for i in range(len(fields)): field = fields[i] if not isinstance(field, Field): dbg(indent = 0, suspend = 0) raise AttributeError('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)) self._fields[index] = field else: dbg(indent = 0, suspend = 0) raise AttributeError('fields parameter must be a list or dictionary; not %s' % repr(fields)) for key in wxMaskedEditMixin.valid_ctrl_params.keys(): if key in ('mask', 'fields'): continue if ctrl_kwargs.has_key(key): setattr(self, '_' + key, ctrl_kwargs[key]) continue for key in ('emptyBackgroundColour', 'invalidBackgroundColour', 'validBackgroundColour', 'foregroundColour', 'signedForegroundColour'): if ctrl_kwargs.has_key(key): if type(ctrl_kwargs[key]) in (types.StringType, types.UnicodeType): c = wxNamedColor(ctrl_kwargs[key]) if c.Get() == (-1, -1, -1): raise TypeError('%s not a legal color specification for %s' % (repr(ctrl_kwargs[key]), key)) else: setattr(self, '_' + key, c) c._name = ctrl_kwargs[key] elif type(ctrl_kwargs[key]) != type(wxBLACK): raise TypeError('%s not a legal color specification for %s' % (repr(ctrl_kwargs[key]), key)) type(ctrl_kwargs[key]) in (types.StringType, types.UnicodeType) dbg('self._retainFieldValidation:', self._retainFieldValidation) if not (self._retainFieldValidation): for arg in Field.propagating_params: if kwargs.has_key(arg): pass reset_args[arg] = kwargs[arg] != getattr(self._ctrl_constraints, '_' + arg) self._ctrl_constraints._SetParameters(**constraint_kwargs) self._configure(mask, **reset_args) self._ctrl_constraints._ValidateParameters(**constraint_kwargs) self._validateChoices() self._autofit = self._ctrl_constraints._autofit self._isNeg = False if 'D' in self._ctrl_constraints._formatcodes: pass self._isDate = isDateType(mask) if 'T' in self._ctrl_constraints._formatcodes: pass self._isTime = isTimeType(mask) if self._isDate: if self._mask.find('CCC') != -1: self._dateExtent = 11 else: self._dateExtent = 10 if len(self._mask) > 8: pass self._4digityear = self._mask[9] == '#' if self._isDate and self._autoformat: if self._autoformat.find('MDDY') != -1: self._datestyle = 'MDY' elif self._autoformat.find('YMMD') != -1: self._datestyle = 'YMD' elif self._autoformat.find('YMMMD') != -1: self._datestyle = 'YMD' elif self._autoformat.find('DMMY') != -1: self._datestyle = 'DMY' elif self._autoformat.find('DMMMY') != -1: self._datestyle = 'DMY' if self.controlInitialized: if kwargs.has_key('useFixedWidthFont'): self._setFont() if reset_args.has_key('reset_mask'): dbg('reset mask') curvalue = self._GetValue() if curvalue.strip(): try: dbg('attempting to _SetInitialValue(%s)' % self._GetValue()) self._SetInitialValue(self._GetValue()) except Exception: e = None dbg('exception caught:', e) dbg("current value doesn't work; attempting to reset to template") self._SetInitialValue() except: None<EXCEPTION MATCH>Exception None<EXCEPTION MATCH>Exception dbg('attempting to _SetInitialValue() with template') self._SetInitialValue() elif kwargs.has_key('useParensForNegatives'): newvalue = self._getSignedValue()[0] if newvalue is not None: if len(newvalue) < len(self._mask): newvalue += ' ' elif len(newvalue) > len(self._mask): if newvalue[-1] in (' ', ')'): newvalue = newvalue[:-1] dbg('reconfiguring value for parens:"%s"' % newvalue) self._SetValue(newvalue) if self._prevValue != newvalue: self._prevValue = newvalue if self._autofit: dbg('setting client size to:', self._CalcSize()) self.SetClientSize(self._CalcSize()) self._applyFormatting() dbg(indent = 0, suspend = 0) def SetMaskParameters(self, **kwargs): return self.SetCtrlParameters(**kwargs) def GetCtrlParameter(self, paramname): if wxMaskedEditMixin.valid_ctrl_params.has_key(paramname.replace('Color', 'Colour')): return getattr(self, '_' + paramname.replace('Color', 'Colour')) elif Field.valid_params.has_key(paramname): return self._ctrl_constraints._GetParameter(paramname) else: TypeError('"%s".GetCtrlParameter: invalid parameter "%s"' % (self.name, paramname)) def GetMaskParameter(self, paramname): return self.GetCtrlParameter(paramname) for param in valid_ctrl_params.keys() + Field.valid_params.keys(): propname = param[0].upper() + param[1:] exec 'def Set%s(self, value): self.SetCtrlParameters(%s=value)' % (propname, param) exec 'def Get%s(self): return self.GetCtrlParameter("%s")' % (propname, param) if param.find('Colour') != -1: propname.replace('Colour', 'Color') exec 'def Set%s(self, value): self.SetCtrlParameters(%s=value)' % (propname, param) exec 'def Get%s(self): return self.GetCtrlParameter("%s")' % (propname, param) continue def SetFieldParameters(self, field_index, **kwargs): if field_index not in self._field_indices: raise IndexError('%s is not a valid field for control "%s".' % (str(field_index), self.name)) self._fields[field_index]._SetParameters(**kwargs) self._configure(self._previous_mask) self._fields[field_index]._ValidateParameters(**kwargs) if self.controlInitialized: if kwargs.has_key('fillChar') or kwargs.has_key('defaultValue'): self._SetInitialValue() if self._autofit: self.SetClientSize(self._CalcSize()) self._applyFormatting() def GetFieldParameter(self, field_index, paramname): if field_index not in self._field_indices: raise IndexError('%s is not a valid field for control "%s".' % (str(field_index), self.name)) elif Field.valid_params.has_key(paramname): return self._fields[field_index]._GetParameter(paramname) else: TypeError('"%s".GetFieldParameter: invalid parameter "%s"' % (self.name, paramname)) def _SetKeycodeHandler(self, keycode, func): self._keyhandlers[keycode] = func def _SetKeyHandler(self, char, func): self._SetKeycodeHandler(ord(char), func) def _AddNavKeycode(self, keycode, handler = None): self._nav.append(keycode) if handler: self._keyhandlers[keycode] = handler def _AddNavKey(self, char, handler = None): self._AddNavKeycode(ord(char), handler) def _GetNavKeycodes(self): return self._nav def _SetNavKeycodes(self, keycode_func_tuples): self._nav = [] for keycode, func in keycode_func_tuples: self._nav.append(keycode) if func: self._keyhandlers[keycode] = func continue def _processMask(self, mask): dbg('_processMask: mask', mask, indent = 1) rex = re.compile('([' + string.join(maskchars, '') + '])\\{(\\d+)\\}') s = mask match = rex.search(s) while match: maskchr = s[match.start(1):match.end(1)] repcount = int(s[match.start(2):match.end(2)]) replacement = string.join(maskchr * repcount, '') s = s[:match.start(1)] + replacement + s[match.end(2) + 1:] match = rex.search(s) self._decimalChar = self._ctrl_constraints._decimalChar self._shiftDecimalChar = self._ctrl_constraints._shiftDecimalChar if isFloatingPoint(s): pass self._isFloat = not (self._ctrl_constraints._validRegex) if isInteger(s): pass self._isInt = not (self._ctrl_constraints._validRegex) if not '-' in self._ctrl_constraints._formatcodes and self._isFloat: pass self._signOk = self._isInt self._useParens = self._ctrl_constraints._useParensForNegatives self._isNeg = False if self._signOk and s[0] != ' ': s = ' ' + s if self._ctrl_constraints._defaultValue and self._ctrl_constraints._defaultValue[0] != ' ': self._ctrl_constraints._defaultValue = ' ' + self._ctrl_constraints._defaultValue self._signpos = 0 if self._useParens: s += ' ' self._ctrl_constraints._defaultValue += ' ' ismasked = { } i = 0 while i < len(s): if s[i] == '\\': ismasked[i] = False if i + 1 < len(s): s = s[:i] + s[i + 1:] if i + 2 < len(s) and s[i + 1] == '\\': s = s[:i] + s[i + 1:] else: ismasked[i] = s[i] in maskchars i += 1 dbg('new mask: "%s"' % s, indent = 0) return (s, ismasked) def _calcFieldExtents(self): self._lookupField = { } if self._mask: self.maskdict = { } for charnum in range(len(self._mask)): self.maskdict[charnum] = self._mask[charnum:charnum + 1] if self._signOk: start = 1 else: start = 0 if self._isFloat: if not self._fields.has_key(0): self._fields[0] = Field() if not self._fields.has_key(1): self._fields[1] = Field() self._decimalpos = string.find(self._mask, '.') dbg('decimal pos =', self._decimalpos) formatcodes = self._fields[0]._GetParameter('formatcodes') if 'R' not in formatcodes: formatcodes += 'R' self._fields[0]._SetParameters(index = 0, extent = (start, self._decimalpos), mask = self._mask[start:self._decimalpos], formatcodes = formatcodes) end = len(self._mask) if self._signOk and self._useParens: end -= 1 self._fields[1]._SetParameters(index = 1, extent = (self._decimalpos + 1, end), mask = self._mask[self._decimalpos + 1:end]) for i in range(self._decimalpos + 1): self._lookupField[i] = 0 for i in range(self._decimalpos + 1, len(self._mask) + 1): self._lookupField[i] = 1 elif self._isInt: if not self._fields.has_key(0): self._fields[0] = Field(index = 0) end = len(self._mask) if self._signOk and self._useParens: end -= 1 self._fields[0]._SetParameters(index = 0, extent = (start, end), mask = self._mask[start:end]) for i in range(len(self._mask) + 1): self._lookupField[i] = 0 else: field_index = 0 pos = 0 i = self._findNextEntry(pos, adjustInsert = False) if i < len(self._mask): for j in range(pos, i + 1): self._lookupField[j] = field_index pos = i while i <= len(self._mask): if self._isMaskChar(i): edit_start = i while i < len(self._mask) and self._isMaskChar(i): self._lookupField[i] = field_index i += 1 edit_end = i self._lookupField[i] = field_index if not self._fields.has_key(field_index): kwargs = Field.valid_params.copy() kwargs['index'] = field_index kwargs['extent'] = (edit_start, edit_end) kwargs['mask'] = self._mask[edit_start:edit_end] self._fields[field_index] = Field(**kwargs) else: self._fields[field_index]._SetParameters(index = field_index, extent = (edit_start, edit_end), mask = self._mask[edit_start:edit_end]) pos = i i = self._findNextEntry(pos, adjustInsert = False) if i > pos: for j in range(pos, i + 1): self._lookupField[j] = field_index if i >= len(self._mask): break continue field_index += 1 indices = self._fields.keys() indices.sort() self._field_indices = indices[1:] 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)) continue def _calcTemplate(self, reset_fillchar, reset_default): default_set = False if self._ctrl_constraints._defaultValue: default_set = True else: for field in self._fields.values(): if field._defaultValue and not reset_default: default_set = True continue dbg('default set?', default_set) if self.controlInitialized: curvalue = list(self._GetValue()) else: curvalue = None if hasattr(self, '_fillChar'): old_fillchars = self._fillChar else: old_fillchars = None if hasattr(self, '_template'): old_template = self._template else: old_template = None self._template = '' self._fillChar = { } reset_value = False for field in self._fields.values(): field._template = '' for pos in range(len(self._mask)): field = self._FindField(pos) (start, end) = field._extent if pos == 0 and self._signOk: self._template = ' ' continue if self._isFloat and pos == self._decimalpos: self._template += self._decimalChar continue self if self._isMaskChar(pos): if field._fillChar != self._ctrl_constraints._fillChar and not reset_fillchar: fillChar = field._fillChar else: fillChar = self._ctrl_constraints._fillChar self._fillChar[pos] = fillChar if self.controlInitialized and old_fillchars and old_fillchars.has_key(pos) and curvalue: if curvalue[pos] == old_fillchars[pos] and old_fillchars[pos] != fillChar: reset_value = True curvalue[pos] = fillChar if not (field._defaultValue) and not (self._ctrl_constraints._defaultValue): self._template += fillChar field._template += fillChar elif field._defaultValue and not reset_default: if len(field._defaultValue) > pos - start: self._template += field._defaultValue[pos - start] field._template += field._defaultValue[pos - start] else: self._template += fillChar field._template += fillChar elif len(self._ctrl_constraints._defaultValue) > pos: self._template += self._ctrl_constraints._defaultValue[pos] field._template += self._ctrl_constraints._defaultValue[pos] else: self._template += fillChar field._template += fillChar not reset_default self._template += self._mask[pos] self._fields[-1]._template = self._template if curvalue: newvalue = string.join(curvalue, '') else: newvalue = None if default_set: self._defaultValue = self._template dbg('self._defaultValue:', self._defaultValue) if not self.IsEmpty(self._defaultValue) and not self.IsValid(self._defaultValue): raise ValueError('Default value of "%s" is not a valid value for control "%s"' % (self._defaultValue, self.name)) if newvalue == old_template: newvalue = self._template reset_value = true else: self._defaultValue = None if reset_value: dbg('resetting value to: "%s"' % newvalue) pos = self._GetInsertionPoint() (sel_start, sel_to) = self._GetSelection() self._SetValue(newvalue) self._SetInsertionPoint(pos) self._SetSelection(sel_start, sel_to) def _propagateConstraints(self, **reset_args): parent_codes = self._ctrl_constraints._formatcodes parent_includes = self._ctrl_constraints._includeChars parent_excludes = self._ctrl_constraints._excludeChars for i in self._field_indices: field = self._fields[i] inherit_args = { } if len(self._field_indices) == 1: inherit_args['formatcodes'] = parent_codes inherit_args['includeChars'] = parent_includes inherit_args['excludeChars'] = parent_excludes else: field_codes = current_codes = field._GetParameter('formatcodes') for c in parent_codes: if c not in field_codes: field_codes += c continue if field_codes != current_codes: inherit_args['formatcodes'] = field_codes include_chars = current_includes = field._GetParameter('includeChars') for c in parent_includes: if not (c in include_chars): include_chars += c continue if include_chars != current_includes: inherit_args['includeChars'] = include_chars exclude_chars = current_excludes = field._GetParameter('excludeChars') for c in parent_excludes: if not (c in exclude_chars): exclude_chars += c continue if exclude_chars != current_excludes: inherit_args['excludeChars'] = exclude_chars if reset_args.has_key('defaultValue') and reset_args['defaultValue']: inherit_args['defaultValue'] = '' for param in Field.propagating_params: if reset_args.has_key(param): inherit_args[param] = self.GetCtrlParameter(param) continue if inherit_args: field._SetParameters(**inherit_args) field._ValidateParameters(**inherit_args) continue def _validateChoices(self): for field in self._fields.values(): if field._choices: index = field._index if len(self._field_indices) == 1 and index == 0 and field._choices == self._ctrl_constraints._choices: dbg('skipping (duplicate) choice validation of field 0') continue (start, end) = field._extent field_length = end - start for choice in field._choices: (valid_paste, ignore, replace_to) = self._validatePaste(choice, start, end) if not valid_paste: raise ValueError('"%s" could not be entered into field %d of control "%s"' % (choice, index, self.name)) continue if replace_to > end: raise ValueError('"%s" will not fit into field %d of control "%s"'(choice, index, self.name)) continue def _configure(self, mask, **reset_args): dbg(suspend = 1) dbg('wxMaskedEditMixin::_configure("%s")' % mask, indent = 1) (self._mask, self.ismasked) = self._processMask(mask) self._masklength = len(self._mask) dbg('mask: "%s"' % self._mask, 'previous mask: "%s"' % self._previous_mask) self._previous_mask = mask self._ctrl_constraints._SetParameters(mask = self._mask, extent = (0, self._masklength)) self._calcFieldExtents() if reset_args.has_key('fillChar'): pass reset_fillchar = reset_args['fillChar'] if reset_args.has_key('defaultValue'): pass reset_default = reset_args['defaultValue'] self._calcTemplate(reset_fillchar, reset_default) self._propagateConstraints(**reset_args) if self._isFloat and self._fields[0]._groupChar == self._decimalChar: raise AttributeError('groupChar (%s) and decimalChar (%s) must be distinct.' % (self._fields[0]._groupChar, self._decimalChar)) if self._signOk: self._signpos = 0 signkeys = [ '-', '+', ' '] if self._useParens: signkeys += [ '(', ')'] for key in signkeys: keycode = ord(key) if not self._keyhandlers.has_key(keycode): self._SetKeyHandler(key, self._OnChangeSign) continue if self._isFloat or self._isInt: if self.controlInitialized: value = self._GetValue() if len(value) < len(self._ctrl_constraints._mask): newvalue = value if self._useParens and len(newvalue) < len(self._ctrl_constraints._mask) and newvalue.find('(') == -1: newvalue += ' ' if self._signOk and len(newvalue) < len(self._ctrl_constraints._mask) and newvalue.find(')') == -1: newvalue = ' ' + newvalue if len(newvalue) < len(self._ctrl_constraints._mask): if self._ctrl_constraints._alignRight: newvalue = newvalue.rjust(len(self._ctrl_constraints._mask)) else: newvalue = newvalue.ljust(len(self._ctrl_constraints._mask)) dbg('old value: "%s"' % value) dbg('new value: "%s"' % newvalue) try: self._SetValue(newvalue) except Exception: e = None dbg('exception raised:', e, 'resetting to initial value') self._SetInitialValue() except: None<EXCEPTION MATCH>Exception None<EXCEPTION MATCH>Exception if len(value) > len(self._ctrl_constraints._mask): newvalue = value if not (self._useParens) and newvalue[-1] == ' ': newvalue = newvalue[:-1] if not (self._signOk) and len(newvalue) > len(self._ctrl_constraints._mask): newvalue = newvalue[1:] if not (self._signOk): (newvalue, signpos, right_signpos) = self._getSignedValue(newvalue) dbg('old value: "%s"' % value) dbg('new value: "%s"' % newvalue) try: self._SetValue(newvalue) except Exception: e = None dbg('exception raised:', e, 'resetting to initial value') self._SetInitialValue() except: None<EXCEPTION MATCH>Exception None<EXCEPTION MATCH>Exception if not (self._signOk) and '(' in value or '-' in value: (newvalue, signpos, right_signpos) = self._getSignedValue(value) dbg('old value: "%s"' % value) dbg('new value: "%s"' % newvalue) try: self._SetValue(newvalue) except e: dbg('exception raised:', e, 'resetting to initial value') self._SetInitialValue() except: None<EXCEPTION MATCH>e None<EXCEPTION MATCH>e if not self._keyhandlers.has_key(WXK_DOWN): self._SetKeycodeHandler(WXK_DOWN, self._OnChangeField) if not self._keyhandlers.has_key(WXK_UP): self._SetKeycodeHandler(WXK_UP, self._OnUpNumeric) if not self._keyhandlers.has_key(ord(self._decimalChar)): self._SetKeyHandler(self._decimalChar, self._OnDecimalPoint) if not self._keyhandlers.has_key(ord(self._shiftDecimalChar)): self._SetKeyHandler(self._shiftDecimalChar, self._OnChangeField) if not self._keyhandlers.has_key(ord(self._fields[0]._groupChar)): self._SetKeyHandler(self._fields[0]._groupChar, self._OnGroupChar) dbg(indent = 0, suspend = 0) def _SetInitialValue(self, value = ''): dbg('wxMaskedEditMixin::_SetInitialValue("%s")' % value, indent = 1) if not value: self._prevValue = self._curValue = self._template try: self._SetValue(self._curValue) except Exception: e = None dbg('exception thrown:', e, indent = 0) raise except: None<EXCEPTION MATCH>Exception None<EXCEPTION MATCH>Exception self._prevValue = self._curValue = value try: self.SetValue(value) except Exception: e = None dbg('exception thrown:', e, indent = 0) raise self._applyFormatting() dbg(indent = 0) def _calcSize(self, size = None): if not size is None: pass cont = size == wxDefaultSize if cont and self._autofit: sizing_text = 'M' * self._masklength if wxPlatform != '__WXMSW__': sizing_text += 'M' if wxPlatform == '__WXMAC__': sizing_text += 'M' (w, h) = self.GetTextExtent(sizing_text) size = (w + 4, self.GetClientSize().height) return size def _setFont(self): if not (self._useFixedWidthFont): self._font = wxSystemSettings_GetFont(wxSYS_DEFAULT_GUI_FONT) else: font = self.GetFont() self._font = wxFont(font.GetPointSize(), wxTELETYPE, font.GetStyle(), font.GetWeight(), font.GetUnderlined()) self.SetFont(self._font) def _OnTextChange(self, event): newvalue = self._GetValue() dbg('wxMaskedEditMixin::_OnTextChange: value: "%s"' % newvalue, indent = 1) bValid = False if self._ignoreChange: dbg(indent = 0) return bValid if newvalue == self._curValue: dbg('ignoring bogus text change event', indent = 0) else: dbg('curvalue: "%s", newvalue: "%s"' % (self._curValue, newvalue)) if self._Change(): if self._signOk and self._isNeg and newvalue.find('-') == -1 and newvalue.find('(') == -1: dbg('clearing self._isNeg') self._isNeg = False (text, self._signpos, self._right_signpos) = self._getSignedValue() self._CheckValid() dbg('calling event.Skip()') event.Skip() bValid = True self._prevValue = self._curValue self._curValue = newvalue dbg(indent = 0) return bValid def _OnKeyDown(self, event): key = event.GetKeyCode() if key in self._nav and event.ControlDown(): dbg('wxMaskedEditMixin::OnKeyDown: calling _OnChar') self._OnChar(event) return None event.Skip() def _OnChar(self, event): dbg('wxMaskedEditMixin::_OnChar', indent = 1) key = event.GetKeyCode() orig_pos = self._GetInsertionPoint() orig_value = self._GetValue() dbg('keycode = ', key) dbg('current pos = ', orig_pos) dbg('current selection = ', self._GetSelection()) if not self._Keypress(key): dbg(indent = 0) return None if not (self._mask) or not self._IsEditable(): event.Skip() dbg(indent = 0) return None if key in self._nav + self._control: if self._keyhandlers.has_key(key): keep_processing = self._keyhandlers[key](event) if self._GetValue() != orig_value: self.modified = True if not keep_processing: dbg(indent = 0) return None self._applyFormatting() dbg(indent = 0) return None pos = self._adjustPos(orig_pos, key) (sel_start, sel_to) = self._GetSelection() dbg('pos, sel_start, sel_to:', pos, sel_start, sel_to) keep_processing = True if pos > len(self.maskdict): dbg('field length exceeded:', pos) keep_processing = False if keep_processing: if self._isMaskChar(pos): okchars = self._getAllowedChars(pos) else: dbg('Not a valid position: pos = ', pos, 'chars=', maskchars) okchars = '' key = self._adjustKey(pos, key) if self._keyhandlers.has_key(key): dbg('using supplied key handler:', self._keyhandlers[key]) keep_processing = self._keyhandlers[key](event) if self._GetValue() != orig_value: self.modified = True if not keep_processing: dbg(indent = 0) return None if key < WXK_SPACE or key > 255: dbg('key < WXK_SPACE or key > 255') event.Skip() keep_processing = False else: field = self._FindField(pos) dbg("key ='%s'" % chr(key)) if chr(key) == ' ': dbg('okSpaces?', field._okSpaces) if chr(key) in field._excludeChars + self._ctrl_constraints._excludeChars: keep_processing = False if keep_processing and self._isCharAllowed(chr(key), pos, checkRegex = True): dbg('key allowed by mask') oldstr = self._GetValue() (newstr, newpos, new_select_to, match_field, match_index) = self._insertKey(chr(key), pos, sel_start, sel_to, self._GetValue(), allowAutoSelect = True) dbg("str with '%s' inserted:" % chr(key), '"%s"' % newstr) if self._ctrl_constraints._validRequired and not self.IsValid(newstr): dbg('not valid; checking to see if adjusted string is:') keep_processing = False if self._isFloat and newstr != self._template: newstr = self._adjustFloat(newstr) dbg('adjusted str:', newstr) if self.IsValid(newstr): dbg('it is!') keep_processing = True wxCallAfter(self._SetInsertionPoint, self._decimalpos) if not keep_processing: dbg('key disallowed by validation') if not wxValidator_IsSilent() and orig_pos == pos: wxBell() if keep_processing: unadjusted = newstr if self._isDate and newstr != self._template: newstr = self._adjustDate(newstr) dbg('adjusted newstr:', newstr) if newstr != orig_value: self.modified = True wxCallAfter(self._SetValue, newstr) if not self.IsDefault() and self._isDate and self._4digityear: year2dig = self._dateExtent - 2 if pos == year2dig and unadjusted[year2dig] != newstr[year2dig]: newpos = pos + 2 wxCallAfter(self._SetInsertionPoint, newpos) if match_field is not None: dbg('matched field') self._OnAutoSelect(match_field, match_index) if new_select_to != newpos: dbg('queuing selection: (%d, %d)' % (newpos, new_select_to)) wxCallAfter(self._SetSelection, newpos, new_select_to) else: newfield = self._FindField(newpos) if newfield != field and newfield._selectOnFieldEntry: dbg('queuing selection: (%d, %d)' % (newfield._extent[0], newfield._extent[1])) wxCallAfter(self._SetSelection, newfield._extent[0], newfield._extent[1]) keep_processing = false elif keep_processing: dbg('char not allowed') keep_processing = False if not wxValidator_IsSilent() and orig_pos == pos: wxBell() self._applyFormatting() if keep_processing and key not in self._nav: pos = self._GetInsertionPoint() next_entry = self._findNextEntry(pos) if pos != next_entry: dbg('moving from %(pos)d to next valid entry: %(next_entry)d' % locals()) wxCallAfter(self._SetInsertionPoint, next_entry) if self._isTemplateChar(pos): self._AdjustField(pos) dbg(indent = 0) def _FindFieldExtent(self, pos = None, getslice = False, value = None): dbg('wxMaskedEditMixin::_FindFieldExtent(pos=%s, getslice=%s)' % (str(pos), str(getslice)), indent = 1) field = self._FindField(pos) if not field: if getslice: return (None, None, '') else: return (None, None) (edit_start, edit_end) = field._extent if getslice: if value is None: value = self._GetValue() slice = value[edit_start:edit_end] dbg('edit_start:', edit_start, 'edit_end:', edit_end, 'slice: "%s"' % slice) dbg(indent = 0) return (edit_start, edit_end, slice) else: dbg('edit_start:', edit_start, 'edit_end:', edit_end) dbg(indent = 0) return (edit_start, edit_end) def _FindField(self, pos = None): if pos is None: pos = self._GetInsertionPoint() elif pos < 0 or pos > self._masklength: raise IndexError('position %s out of range of control' % str(pos)) if len(self._fields) == 0: dbg(indent = 0) return None return self._fields[self._lookupField[pos]] def ClearValue(self): dbg('wxMaskedEditMixin::ClearValue - value reset to default value (template)') self._SetValue(self._template) self._SetInsertionPoint(0) self.Refresh() def _baseCtrlEventHandler(self, event): event.Skip() return False def _OnUpNumeric(self, event): dbg('wxMaskedEditMixin::_OnUpNumeric', indent = 1) event.m_shiftDown = 1 dbg('event.ShiftDown()?', event.ShiftDown()) self._OnChangeField(event) dbg(indent = 0) def _OnArrow(self, event): dbg('wxMaskedEditMixin::_OnArrow', indent = 1) pos = self._GetInsertionPoint() keycode = event.GetKeyCode() (sel_start, sel_to) = self._GetSelection() entry_end = self._goEnd(getPosOnly = True) if keycode in (WXK_RIGHT, WXK_DOWN): if not self._isTemplateChar(pos) and pos + 1 > entry_end and self._isTemplateChar(pos) and pos >= entry_end: dbg("can't advance", indent = 0) return False elif self._isTemplateChar(pos): self._AdjustField(pos) elif keycode in (WXK_LEFT, WXK_UP) and sel_start == sel_to and pos > 0 and self._isTemplateChar(pos - 1): dbg('adjusting field') self._AdjustField(pos) if event.ShiftDown() and keycode in (WXK_UP, WXK_DOWN): event.m_shiftDown = False keep_processing = self._OnChangeField(event) elif self._FindField(pos)._selectOnFieldEntry: if keycode in (WXK_UP, WXK_LEFT) and sel_start != 0 and self._isTemplateChar(sel_start - 1) and sel_start != self._masklength and not (self._signOk) and not (self._useParens): event.m_shiftDown = True event.m_ControlDown = True keep_processing = self._OnChangeField(event) elif keycode in (WXK_DOWN, WXK_RIGHT) and sel_to != self._masklength and self._isTemplateChar(sel_to): event.m_shiftDown = False keep_processing = self._OnChangeField(event) else: dbg('using base ctrl event processing') event.Skip() elif sel_to == self._fields[0]._extent[0] and keycode == WXK_LEFT and sel_to == self._masklength and keycode == WXK_RIGHT: if not wxValidator_IsSilent(): wxBell() else: dbg('using base event processing') event.Skip() keep_processing = False dbg(indent = 0) return keep_processing def _OnCtrl_S(self, event): dbg('wxMaskedEditMixin::_OnCtrl_S') if self._demo: print 'wxMaskedEditMixin.GetValue() = "%s"\nwxMaskedEditMixin.GetPlainValue() = "%s"' % (self.GetValue(), self.GetPlainValue()) print 'Valid? => ' + str(self.IsValid()) print 'Current field, start, end, value =', str(self._FindFieldExtent(getslice = True)) return False def _OnCtrl_X(self, event = None): dbg('wxMaskedEditMixin::_OnCtrl_X', indent = 1) self.Cut() dbg(indent = 0) return False def _OnCtrl_C(self, event = None): self.Copy() return False def _OnCtrl_V(self, event = None): dbg('wxMaskedEditMixin::_OnCtrl_V', indent = 1) self.Paste() dbg(indent = 0) return False def _OnCtrl_Z(self, event = None): dbg('wxMaskedEditMixin::_OnCtrl_Z', indent = 1) self.Undo() dbg(indent = 0) return False def _OnCtrl_A(self, event = None): end = self._goEnd(getPosOnly = True) if not event or event.ShiftDown(): wxCallAfter(self._SetInsertionPoint, 0) wxCallAfter(self._SetSelection, 0, self._masklength) else: wxCallAfter(self._SetInsertionPoint, 0) wxCallAfter(self._SetSelection, 0, end) return False def _OnErase(self, event = None): dbg('wxMaskedEditMixin::_OnErase', indent = 1) (sel_start, sel_to) = self._GetSelection() if event is None: key = WXK_DELETE else: key = event.GetKeyCode() field = self._FindField(sel_to) (start, end) = field._extent value = self._GetValue() oldstart = sel_start if not sel_to == 0 and key == WXK_BACK: if not self._signOk and sel_to == 1 and value[0] == ' ' and key == WXK_BACK: if sel_to == self._masklength and sel_start == sel_to and key == WXK_DELETE and not (field._insertRight) and self._signOk and self._useParens and sel_start == sel_to and sel_to == self._masklength - 1 and value[sel_to] == ' ' and key == WXK_DELETE and not (field._insertRight): if not wxValidator_IsSilent(): wxBell() dbg(indent = 0) return False if field._insertRight and value[start:end] != self._template[start:end] and sel_start >= start: if sel_to == sel_start and sel_to == end and key in (WXK_BACK, WXK_DELETE) and key == WXK_BACK and sel_to == end and sel_to < end and field._allowInsert: dbg('delete left') while key == WXK_BACK and sel_start == sel_to and sel_start < end and value[start:sel_start] == self._template[start:sel_start]: sel_start += 1 sel_to = sel_start dbg('sel_start, start:', sel_start, start) if sel_start == sel_to: keep = sel_start - 1 else: keep = sel_start newfield = value[start:keep] + value[sel_to:end] move_sign_into_field = False if not (field._padZero) and self._signOk and self._isNeg and value[0] in ('-', '('): signchar = value[0] newfield = signchar + newfield move_sign_into_field = True dbg('cut newfield: "%s"' % newfield) left = '' for i in range(start, end - len(newfield)): if field._padZero: left += '0' continue if self._signOk and self._isNeg and i == 1: if self._useParens and newfield.find('(') == -1 and not (self._useParens) and newfield.find('-') == -1: left += ' ' continue left += self._template[i] newfield = left + newfield dbg('filled newfield: "%s"' % newfield) newstr = value[:start] + newfield + value[end:] if move_sign_into_field: newstr = ' ' + newstr[1:] pos = sel_to elif self._signOk and sel_start == 0: newstr = value = ' ' + value[1:] sel_start += 1 if field._allowInsert and sel_start >= start: select_len = sel_to - sel_start if key == WXK_BACK: if select_len == 0: newpos = sel_start - 1 else: newpos = sel_start erase_to = sel_to else: newpos = sel_start if sel_to == sel_start: erase_to = sel_to + 1 else: erase_to = sel_to if self._isTemplateChar(newpos) and select_len == 0: if self._signOk: if value[newpos] in ('(', '-'): newpos += 1 newstr = ' ' + value[newpos:] elif value[newpos] == ')': newstr = value[:newpos] + ' ' else: newstr = value else: newstr = value elif erase_to > end: erase_to = end erase_len = erase_to - newpos left = value[start:newpos] dbg("retained ='%s'" % value[erase_to:end], 'sel_to:', sel_to, "fill: '%s'" % self._template[end - erase_len:end]) right = value[erase_to:end] + self._template[end - erase_len:end] pos_adjust = 0 if field._alignRight: rstripped = right.rstrip() if rstripped != right: pos_adjust = len(right) - len(rstripped) right = rstripped if not (field._insertRight) and value[-1] == ')' and end == self._masklength - 1: right = right[:-1] + ')' value = value[:-1] + ' ' newfield = left + right if pos_adjust: newfield = newfield.rjust(end - start) newpos += pos_adjust dbg("left='%s', right ='%s', newfield='%s'" % (left, right, newfield)) newstr = value[:start] + newfield + value[end:] pos = newpos elif sel_start == sel_to: dbg('current sel_start, sel_to:', sel_start, sel_to) if key == WXK_BACK: (sel_start, sel_to) = (sel_to - 1, sel_to - 1) dbg('new sel_start, sel_to:', sel_start, sel_to) if field._padZero and not value[start:sel_to].replace('0', '').replace(' ', '').replace(field._fillChar, ''): newchar = '0' else: newchar = self._template[sel_to] dbg('value = "%s"' % value, 'value[%d] = "%s"' % (sel_start, value[sel_start])) if self._isTemplateChar(sel_to): if sel_to == 0 and self._signOk and value[sel_to] == '-': newstr = ' ' + value[1:] sel_to += 1 elif self._signOk and self._useParens and value[sel_to] == ')' or value[sel_to] == '(': newstr = value[:self._signpos] + ' ' + value[self._signpos + 1:-1] + ' ' else: newstr = value newpos = sel_to elif field._insertRight and sel_start == sel_to: sel_to += 1 (newstr, ignore) = self._insertKey(newchar, sel_start, sel_start, sel_to, value) else: newstr = self._eraseSelection(value, sel_start, sel_to) pos = sel_start if self._signOk and self._useParens: left_signpos = newstr.find('(') right_signpos = newstr.find(')') if left_signpos == -1 and right_signpos != -1: newstr = newstr[:right_signpos] + ' ' + newstr[right_signpos + 1:] elif left_signpos != -1 and right_signpos == -1: newstr = newstr[:left_signpos] + ' ' + newstr[left_signpos + 1:] dbg("oldstr:'%s'" % value, 'oldpos:', oldstart) dbg("newstr:'%s'" % newstr, 'pos:', pos) dbg('field._validRequired?', field._validRequired) dbg('field.IsValid("%s")?' % newstr[start:end], field.IsValid(newstr[start:end])) if field._validRequired and not field.IsValid(newstr[start:end]): if not wxValidator_IsSilent(): wxBell() dbg(indent = 0) return False if self._ctrl_constraints._validRequired and not self.IsValid(newstr): if not wxValidator_IsSilent(): wxBell() dbg(indent = 0) return False dbg('setting value (later) to', newstr) wxCallAfter(self._SetValue, newstr) dbg('setting insertion point (later) to', pos) wxCallAfter(self._SetInsertionPoint, pos) dbg(indent = 0) return False def _OnEnd(self, event): dbg('wxMaskedEditMixin::_OnEnd', indent = 1) pos = self._adjustPos(self._GetInsertionPoint(), event.GetKeyCode()) if not event.ControlDown(): end = self._masklength if self._signOk and self._useParens: end = end - 1 else: end_of_input = self._goEnd(getPosOnly = True) (sel_start, sel_to) = self._GetSelection() if sel_to < pos: sel_to = pos field = self._FindField(sel_to) field_end = self._FindField(end_of_input) if field != field_end or sel_to >= end_of_input: (edit_start, edit_end) = field._extent if sel_to == edit_end and field._index < self._field_indices[-1]: (edit_start, edit_end) = self._FindFieldExtent(self._findNextEntry(edit_end)) end = edit_end dbg('end moved to', end) elif sel_to == edit_end and field._index == self._field_indices[-1]: end = self._masklength dbg('end moved to', end) else: end = edit_end dbg('end moved to ', end) else: end = end_of_input if event.ShiftDown(): if not event.ControlDown(): dbg('shift-end; select to end of control') else: dbg('shift-ctrl-end; select to end of non-whitespace') wxCallAfter(self._SetInsertionPoint, pos) wxCallAfter(self._SetSelection, pos, end) elif not event.ControlDown(): dbg('go to end of control:') wxCallAfter(self._SetInsertionPoint, end) wxCallAfter(self._SetSelection, end, end) dbg(indent = 0) return False def _OnReturn(self, event): dbg('wxMaskedEditMixin::OnReturn') event.m_keyCode = WXK_TAB event.Skip() def _OnHome(self, event): dbg('wxMaskedEditMixin::_OnHome', indent = 1) pos = self._adjustPos(self._GetInsertionPoint(), event.GetKeyCode()) (sel_start, sel_to) = self._GetSelection() if event.ShiftDown() and not event.ControlDown(): dbg('shift-home; select to start of control') start = 0 end = sel_start elif not event.ControlDown(): dbg('home; move to start of control') start = 0 end = 0 elif event.ControlDown() and not event.ShiftDown() and event.ShiftDown() and sel_start > 0: if len(self._field_indices) > 1: field = self._FindField(sel_start) (start, ignore) = field._extent if sel_start == start and field._index != self._field_indices[0]: (start, ignore) = self._FindFieldExtent(sel_start - 1) elif sel_start == start: start = 0 end_of_field = True else: start = 0 if not event.ShiftDown(): dbg('ctrl-home; move to beginning of field') end = start else: dbg('shift-ctrl-home; select to beginning of field') end = sel_to else: start = sel_start if len(self._field_indices) > 1: field = self._FindField(sel_to) if sel_to > start and field._index != self._field_indices[0]: (ignore, end) = self._FindFieldExtent(field._extent[0] - 1) else: end = start end_of_field = True else: end = start end_of_field = False dbg('shift-ctrl-home; unselect to beginning of field') dbg('queuing new sel_start, sel_to:', (start, end)) wxCallAfter(self._SetInsertionPoint, start) wxCallAfter(self._SetSelection, start, end) dbg(indent = 0) return False def _OnChangeField(self, event): dbg('wxMaskedEditMixin::_OnChangeField', indent = 1) pos = self._GetInsertionPoint() dbg('current pos:', pos) (sel_start, sel_to) = self._GetSelection() if self._masklength < 0: self._AdjustField(pos) if event.GetKeyCode() == WXK_TAB: dbg('tab to next ctrl') event.Skip() dbg(indent = 0) return False if event.ShiftDown(): field = self._FindField(pos) index = field._index field_start = field._extent[0] if pos < field_start: dbg('cursor before 1st field; cannot change to a previous field') if not wxValidator_IsSilent(): wxBell() return false if event.ControlDown(): dbg('queuing select to beginning of field:', field_start, pos) wxCallAfter(self._SetInsertionPoint, field_start) wxCallAfter(self._SetSelection, field_start, pos) dbg(indent = 0) return False elif index == 0: self._AdjustField(pos) if event.GetKeyCode() == WXK_TAB: dbg('tab to previous ctrl') event.Skip() else: dbg('position at beginning') wxCallAfter(self._SetInsertionPoint, field_start) dbg(indent = 0) return False else: begin_prev = self._FindField(field_start - 1)._extent[0] self._AdjustField(pos) dbg('repositioning to', begin_prev) wxCallAfter(self._SetInsertionPoint, begin_prev) if self._FindField(begin_prev)._selectOnFieldEntry: (edit_start, edit_end) = self._FindFieldExtent(begin_prev) dbg('queuing selection to (%d, %d)' % (edit_start, edit_end)) wxCallAfter(self._SetInsertionPoint, edit_start) wxCallAfter(self._SetSelection, edit_start, edit_end) dbg(indent = 0) return False else: field = self._FindField(sel_to) (field_start, field_end) = field._extent if event.ControlDown(): dbg('queuing select to end of field:', pos, field_end) wxCallAfter(self._SetInsertionPoint, pos) wxCallAfter(self._SetSelection, pos, field_end) dbg(indent = 0) return False elif pos < field_start: dbg('cursor before 1st field; go to start of field') wxCallAfter(self._SetInsertionPoint, field_start) if field._selectOnFieldEntry: wxCallAfter(self._SetSelection, field_start, field_end) else: wxCallAfter(self._SetSelection, field_start, field_start) return False dbg('end of current field:', field_end) dbg('go to next field') if field_end == self._fields[self._field_indices[-1]]._extent[1]: self._AdjustField(pos) if event.GetKeyCode() == WXK_TAB: dbg('tab to next ctrl') event.Skip() else: dbg('position at end') wxCallAfter(self._SetInsertionPoint, field_end) dbg(indent = 0) return False else: next_pos = self._findNextEntry(field_end) if next_pos == field_end: dbg('already in last field') self._AdjustField(pos) if event.GetKeyCode() == WXK_TAB: dbg('tab to next ctrl') event.Skip() dbg(indent = 0) return False else: self._AdjustField(pos) field = self._FindField(next_pos) (edit_start, edit_end) = field._extent if field._selectOnFieldEntry: dbg('move to ', next_pos) wxCallAfter(self._SetInsertionPoint, next_pos) (edit_start, edit_end) = self._FindFieldExtent(next_pos) dbg('queuing select', edit_start, edit_end) wxCallAfter(self._SetSelection, edit_start, edit_end) elif field._insertRight: next_pos = field._extent[1] dbg('move to ', next_pos) wxCallAfter(self._SetInsertionPoint, next_pos) dbg(indent = 0) return False def _OnDecimalPoint(self, event): dbg('wxMaskedEditMixin::_OnDecimalPoint', indent = 1) pos = self._adjustPos(self._GetInsertionPoint(), event.GetKeyCode()) if self._isFloat: dbg('key == Decimal tab; decimal pos:', self._decimalpos) value = self._GetValue() if pos < self._decimalpos: clipped_text = value[0:pos] + self._decimalChar + value[self._decimalpos + 1:] dbg('value: "%s"' % self._GetValue(), "clipped_text:'%s'" % clipped_text) newstr = self._adjustFloat(clipped_text) else: newstr = self._adjustFloat(value) wxCallAfter(self._SetValue, newstr) fraction = self._fields[1] (start, end) = fraction._extent wxCallAfter(self._SetInsertionPoint, start) if fraction._selectOnFieldEntry: dbg('queuing selection after decimal point to:', (start, end)) wxCallAfter(self._SetSelection, start, end) keep_processing = False if self._isInt: dbg('key == Integer decimal event') value = self._GetValue() clipped_text = value[0:pos] dbg('value: "%s"' % self._GetValue(), "clipped_text:'%s'" % clipped_text) newstr = self._adjustInt(clipped_text) dbg('newstr: "%s"' % newstr) wxCallAfter(self._SetValue, newstr) newpos = len(newstr.rstrip()) if newstr.find(')') != -1: newpos -= 1 wxCallAfter(self._SetInsertionPoint, newpos) keep_processing = False dbg(indent = 0) def _OnChangeSign(self, event): dbg('wxMaskedEditMixin::_OnChangeSign', indent = 1) key = event.GetKeyCode() pos = self._adjustPos(self._GetInsertionPoint(), key) value = self._eraseSelection() integer = self._fields[0] (start, end) = integer._extent if chr(key) in ('-', '+', '(', ')') and chr(key) == ' ' and pos == self._signpos: cursign = self._isNeg dbg('cursign:', cursign) if chr(key) in ('-', '(', ')'): self._isNeg = not (self._isNeg) else: self._isNeg = False dbg('isNeg?', self._isNeg) (text, self._signpos, self._right_signpos) = self._getSignedValue(candidate = value) dbg('text:"%s"' % text, 'signpos:', self._signpos, 'right_signpos:', self._right_signpos) if text is None: text = value if self._isNeg and self._signpos is not None and self._signpos != -1: if self._useParens and self._right_signpos is not None: text = text[:self._signpos] + '(' + text[self._signpos + 1:self._right_signpos] + ')' + text[self._right_signpos + 1:] else: text = text[:self._signpos] + '-' + text[self._signpos + 1:] elif self._useParens: text = text[:self._signpos] + ' ' + text[self._signpos + 1:self._right_signpos] + ' ' + text[self._right_signpos + 1:] else: text = text[:self._signpos] + ' ' + text[self._signpos + 1:] dbg('clearing self._isNeg') self._isNeg = False wxCallAfter(self._SetValue, text) wxCallAfter(self._applyFormatting) dbg('pos:', pos, 'signpos:', self._signpos) if pos == self._signpos or integer.IsEmpty(text[start:end]): wxCallAfter(self._SetInsertionPoint, self._signpos + 1) else: wxCallAfter(self._SetInsertionPoint, pos) keep_processing = False else: keep_processing = True dbg(indent = 0) return keep_processing def _OnGroupChar(self, event): dbg('wxMaskedEditMixin::_OnGroupChar', indent = 1) keep_processing = True pos = self._adjustPos(self._GetInsertionPoint(), event.GetKeyCode()) (sel_start, sel_to) = self._GetSelection() groupchar = self._fields[0]._groupChar if not self._isCharAllowed(groupchar, pos, checkRegex = True): keep_processing = False if not wxValidator_IsSilent(): wxBell() if keep_processing: (newstr, newpos) = self._insertKey(groupchar, pos, sel_start, sel_to, self._GetValue()) dbg("str with '%s' inserted:" % groupchar, '"%s"' % newstr) if self._ctrl_constraints._validRequired and not self.IsValid(newstr): keep_processing = False if not wxValidator_IsSilent(): wxBell() if keep_processing: wxCallAfter(self._SetValue, newstr) wxCallAfter(self._SetInsertionPoint, newpos) keep_processing = False dbg(indent = 0) return keep_processing def _findNextEntry(self, pos, adjustInsert = True): if self._isTemplateChar(pos): adjustInsert = adjustInsert else: adjustInsert = False while self._isTemplateChar(pos) and pos < self._masklength: pos += 1 if adjustInsert and pos < self._masklength: field = self._FindField(pos) (start, end) = field._extent slice = self._GetValue()[start:end] if field._insertRight and field.IsEmpty(slice): pos = end return pos def _findNextTemplateChar(self, pos): while not self._isTemplateChar(pos) and pos < self._masklength: pos += 1 return pos def _OnAutoCompleteField(self, event): dbg('wxMaskedEditMixin::_OnAutoCompleteField', indent = 1) pos = self._GetInsertionPoint() field = self._FindField(pos) (edit_start, edit_end, slice) = self._FindFieldExtent(pos, getslice = True) match_index = None keycode = event.GetKeyCode() if field._fillChar != ' ': text = slice.replace(field._fillChar, '') else: text = slice text = text.strip() keep_processing = True dbg('field._hasList?', field._hasList) if field._hasList: dbg('choices:', field._choices) dbg('compareChoices:', field._compareChoices) (choices, choice_required) = (field._compareChoices, field._choiceRequired) if keycode in (WXK_PRIOR, WXK_UP): direction = -1 else: direction = 1 (match_index, partial_match) = self._autoComplete(direction, choices, text, compareNoCase = field._compareNoCase, current_index = field._autoCompleteIndex) if match_index is None and keycode in self._autoCompleteKeycodes + [ WXK_PRIOR, WXK_NEXT] and keycode in [ WXK_UP, WXK_DOWN] and event.ShiftDown(): match_index = 0 if not match_index is not None and keycode in self._autoCompleteKeycodes + [ WXK_PRIOR, WXK_NEXT]: if keycode in [ WXK_UP, WXK_DOWN] and event.ShiftDown() and keycode == WXK_DOWN and partial_match: dbg('match found') value = self._GetValue() newvalue = value[:edit_start] + field._choices[match_index] + value[edit_end:] dbg('setting value to "%s"' % newvalue) self._SetValue(newvalue) self._SetInsertionPoint(min(edit_end, len(newvalue.rstrip()))) self._OnAutoSelect(field, match_index) self._CheckValid() if keycode in (WXK_UP, WXK_DOWN, WXK_LEFT, WXK_RIGHT): if event.ShiftDown(): if keycode in (WXK_DOWN, WXK_RIGHT): event.m_shiftDown = False keep_processing = self._OnChangeField(event) else: keep_processing = self._OnArrow(event) dbg('keep processing?', keep_processing, indent = 0) return keep_processing def _OnAutoSelect(self, field, match_index = None): dbg('wxMaskedEditMixin::OnAutoSelect', field._index) if match_index is not None: field._autoCompleteIndex = match_index def _autoComplete(self, direction, choices, value, compareNoCase, current_index): dbg('autoComplete(direction=', direction, 'choices=', choices, 'value=', value, 'compareNoCase?', compareNoCase, 'current_index:', current_index, indent = 1) if value is None: dbg('nothing to match against', indent = 0) return (None, False) partial_match = False if compareNoCase: value = value.lower() last_index = len(choices) - 1 if value in choices: dbg('"%s" in', choices) if current_index is not None and choices[current_index] == value: index = current_index else: index = choices.index(value) dbg('matched "%s" (%d)' % (choices[index], index)) if direction == -1: dbg('going to previous') if index == 0: index = len(choices) - 1 else: index -= 1 elif index == len(choices) - 1: index = 0 else: index += 1 dbg('change value to "%s" (%d)' % (choices[index], index)) match = index else: partial_match = True value = value.strip() dbg('no match; try to auto-complete:') match = None dbg('searching for "%s"' % value) if current_index is None: indices = range(len(choices)) if direction == -1: indices.reverse() elif direction == 1: indices = range(current_index + 1, len(choices)) + range(current_index + 1) dbg('range(current_index+1 (%d), len(choices) (%d)) + range(%d):' % (current_index + 1, len(choices), current_index + 1), indices) else: indices = range(current_index - 1, -1, -1) + range(len(choices) - 1, current_index - 1, -1) dbg('range(current_index-1 (%d), -1) + range(len(choices)-1 (%d)), current_index-1 (%d):' % (current_index - 1, len(choices) - 1, current_index - 1), indices) for index in indices: choice = choices[index] if choice.find(value, 0) == 0: dbg('match found:', choice) match = index break continue dbg('choice: "%s" - no match' % choice) if match is not None: dbg('matched', match) else: dbg('no match found') dbg(indent = 0) return (match, partial_match) def _AdjustField(self, pos): newvalue = value = self._GetValue() field = self._FindField(pos) (start, end, slice) = self._FindFieldExtent(getslice = True) newfield = field._AdjustField(slice) newvalue = value[:start] + newfield + value[end:] if self._isFloat and newvalue != self._template: newvalue = self._adjustFloat(newvalue) if self._ctrl_constraints._isInt and value != self._template: newvalue = self._adjustInt(value) if self._isDate and value != self._template: newvalue = self._adjustDate(value, fixcentury = True) if self._4digityear: year2dig = self._dateExtent - 2 if pos == year2dig and value[year2dig] != newvalue[year2dig]: pos = pos + 2 if newvalue != value: self._SetValue(newvalue) self._SetInsertionPoint(pos) def _adjustKey(self, pos, key): field = self._FindField(pos) if field._forceupper and key in range(97, 123): key = ord(chr(key).upper()) if field._forcelower and key in range(97, 123): key = ord(chr(key).lower()) return key def _adjustPos(self, pos, key): dbg('_adjustPos', pos, key, indent = 1) (sel_start, sel_to) = self._GetSelection() if self._signOk: if pos == self._signpos and key in (ord('-'), ord('+'), ord(' ')) and self._useParens and pos == self._masklength - 1: dbg('adjusted pos:', pos, indent = 0) return pos if key not in self._nav: field = self._FindField(pos) dbg('field._insertRight?', field._insertRight) if field._insertRight: (start, end) = field._extent slice = self._GetValue()[start:end].strip() field_len = end - start if pos == end: if len(slice) == field_len and field._moveOnFieldFull: pos = self._findNextEntry(pos) self._SetInsertionPoint(pos) if pos < sel_to: self._SetSelection(pos, sel_to) else: self._SetSelection(pos, pos) elif sel_to == sel_start and self._isTemplateChar(pos) and pos != end: pos = end elif self._signOk and sel_start == 0: pos = self._fields[0]._extent[0] self._SetInsertionPoint(pos) self._SetSelection(pos, sel_to) elif self._isTemplateChar(pos): if not (field._moveOnFieldFull) and not (self._signOk) and self._signOk and field._index == 0 and pos > 0: pass else: pos = self._findNextEntry(pos) self._SetInsertionPoint(pos) if pos < sel_to: self._SetSelection(pos, sel_to) dbg('adjusted pos:', pos, indent = 0) return pos def _adjustFloat(self, candidate = None): dbg('wxMaskedEditMixin::_adjustFloat, candidate = "%s"' % candidate, indent = 1) (lenInt, lenFraction) = [ len(s) for s in self._mask.split('.') ] dbg('value = "%(value)s"' % locals(), 'len(value):', len(value)) (intStr, fracStr) = value.split(self._decimalChar) intStr = self._fields[0]._AdjustField(intStr) dbg('adjusted intStr: "%s"' % intStr) lenInt = len(intStr) fracStr = fracStr + '0' * (lenFraction - len(fracStr)) dbg('intStr "%(intStr)s"' % locals()) dbg('lenInt:', lenInt) intStr = string.rjust(intStr[-lenInt:], lenInt) dbg('right-justifed intStr = "%(intStr)s"' % locals()) newvalue = intStr + self._decimalChar + fracStr if self._signOk: if len(newvalue) < self._masklength: newvalue = ' ' + newvalue signedvalue = self._getSignedValue(newvalue)[0] if signedvalue is not None: newvalue = signedvalue newdecpos = newvalue.find(self._decimalChar) if newdecpos < self._decimalpos: padlen = self._decimalpos - newdecpos newvalue = string.join([ ' ' * padlen] + [ newvalue], '') if self._signOk and self._useParens: if newvalue.find('(') != -1: newvalue = newvalue[:-1] + ')' else: newvalue = newvalue[:-1] + ' ' dbg('newvalue = "%s"' % newvalue) if candidate is None: wxCallAfter(self._SetValue, newvalue) dbg(indent = 0) return newvalue def _adjustInt(self, candidate = None): dbg('wxMaskedEditMixin::_adjustInt', candidate) lenInt = self._masklength if candidate is None: value = self._GetValue() else: value = candidate intStr = self._fields[0]._AdjustField(value) intStr = intStr.strip() dbg('adjusted field: "%s"' % intStr) if self._isNeg and intStr.find('-') == -1 and intStr.find('(') == -1: if self._useParens: intStr = '(' + intStr + ')' else: intStr = '-' + intStr elif self._isNeg and intStr.find('-') != -1 and self._useParens: intStr = intStr.replace('-', '(') if self._signOk: if self._useParens and intStr.find('(') == -1 and not (self._useParens) and intStr.find('-') == -1: intStr = ' ' + intStr if self._useParens: intStr += ' ' elif self._signOk and self._useParens and intStr.find('(') != -1 and intStr.find(')') == -1: intStr += ')' if self._fields[0]._alignRight: intStr = intStr.rjust(lenInt) else: intStr = intStr.ljust(lenInt) if candidate is None: wxCallAfter(self._SetValue, intStr) return intStr def _adjustDate(self, candidate = None, fixcentury = False, force4digit_year = False): dbg('wxMaskedEditMixin::_adjustDate', indent = 1) if candidate is None: text = self._GetValue() else: text = candidate dbg('text=', text) if self._datestyle == 'YMD': year_field = 0 else: year_field = 2 dbg('getYear: "%s"' % getYear(text, self._datestyle)) year = string.replace(getYear(text, self._datestyle), self._fields[year_field]._fillChar, '') month = getMonth(text, self._datestyle) day = getDay(text, self._datestyle) dbg('self._datestyle:', self._datestyle, 'year:', year, 'Month', month, 'day:', day) yearVal = None yearstart = self._dateExtent - 4 if not len(year) < 4 and fixcentury and force4digit_year: if self._GetInsertionPoint() > yearstart + 1 and text[yearstart + 2] == ' ' and self._GetInsertionPoint() > yearstart + 2 and text[yearstart + 3] == ' ': try: yearVal = int(year) dbg('bad year=', year) year = text[yearstart:self._dateExtent] if len(year) < 4 and yearVal: if len(year) == 2: now = wxDateTime_Now() century = (now.GetYear() / 100) * 100 twodig_year = now.GetYear() - century if abs(yearVal - twodig_year) > 50: yearVal = (century - 100) + yearVal else: yearVal = century + yearVal year = str(yearVal) else: year = '%04d' % yearVal if self._4digityear or force4digit_year: text = makeDate(year, month, day, self._datestyle, text) + text[self._dateExtent:] dbg('newdate: "%s"' % text, indent = 0) return text def _goEnd(self, getPosOnly = False): dbg('wxMaskedEditMixin::_goEnd; getPosOnly:', getPosOnly, indent = 1) text = self._GetValue() i = 0 if len(text.rstrip()): for i in range(min(self._masklength - 1, len(text.rstrip())), -1, -1): if self._isMaskChar(i): char = text[i] if char != ' ': i += 1 break char != ' ' if i == 0: pos = self._goHome(getPosOnly = True) else: pos = min(i, self._masklength) field = self._FindField(pos) (start, end) = field._extent if field._insertRight and pos < end: pos = end dbg('next pos:', pos) dbg(indent = 0) if getPosOnly: return pos else: self._SetInsertionPoint(pos) def _goHome(self, getPosOnly = False): dbg('wxMaskedEditMixin::_goHome; getPosOnly:', getPosOnly, indent = 1) text = self._GetValue() for i in range(self._masklength): if self._isMaskChar(i): break continue pos = max(i, 0) dbg(indent = 0) if getPosOnly: return pos else: self._SetInsertionPoint(max(i, 0)) def _getAllowedChars(self, pos): maskChar = self.maskdict[pos] okchars = self.maskchardict[maskChar] field = self._FindField(pos) if okchars and field._okSpaces: okchars += ' ' if okchars and field._includeChars: okchars += field._includeChars return okchars def _isMaskChar(self, pos): if pos < self._masklength: return self.ismasked[pos] else: return False def _isTemplateChar(self, Pos): if Pos < self._masklength: return not self._isMaskChar(Pos) else: return False def _isCharAllowed(self, char, pos, checkRegex = False, allowAutoSelect = True, ignoreInsertRight = False): dbg('_isCharAllowed', char, pos, checkRegex, indent = 1) field = self._FindField(pos) right_insert = False if self.controlInitialized: (sel_start, sel_to) = self._GetSelection() else: (sel_start, sel_to) = (pos, pos) if (field._insertRight or self._ctrl_constraints._insertRight) and not ignoreInsertRight: (start, end) = field._extent field_len = end - start if self.controlInitialized: value = self._GetValue() fstr = value[start:end].strip() if field._padZero: while fstr and fstr[0] == '0': fstr = fstr[1:] input_len = len(fstr) if self._signOk and '-' in fstr or '(' in fstr: input_len -= 1 else: value = self._template input_len = 0 if (sel_start, sel_to) == field._extent and pos == end and input_len < field_len: pos = end - 1 dbg('pos = end - 1 = ', pos, 'right_insert? 1') right_insert = True elif field._allowInsert and sel_start == sel_to: if sel_to == end and sel_to < self._masklength and value[sel_start] != field._fillChar and input_len < field_len: pos = sel_to - 1 dbg('pos = sel_to - 1 = ', pos, 'right_insert? 1') right_insert = True else: dbg('pos stays ', pos, 'right_insert? 0') if self._isTemplateChar(pos): dbg('%d is a template character; returning false' % pos, indent = 0) return False if self._isMaskChar(pos): okChars = self._getAllowedChars(pos) if self._fields[0]._groupdigits and self._isInt and self._isFloat and pos < self._decimalpos: okChars += self._fields[0]._groupChar if self._signOk: if self._isInt and self._isFloat and pos < self._decimalpos: okChars += '-' if self._useParens: okChars += '(' elif self._useParens and self._isInt and self._isFloat and pos > self._decimalpos: okChars += ')' approved = char in okChars if approved and checkRegex: dbg("checking appropriate regex's") value = self._eraseSelection(self._GetValue()) if right_insert: at = pos + 1 else: at = pos if allowAutoSelect: (newvalue, ignore, ignore, ignore, ignore) = self._insertKey(char, at, sel_start, sel_to, value, allowAutoSelect = True) else: (newvalue, ignore) = self._insertKey(char, at, sel_start, sel_to, value) dbg('newvalue: "%s"' % newvalue) fields = [ self._FindField(pos)] + [ self._ctrl_constraints] for field in fields: if field._regexMask and field._filter: dbg('checking vs. regex') (start, end) = field._extent slice = newvalue[start:end] approved = re.match(field._filter, slice) is not None dbg('approved?', approved) if not approved: break continue dbg(indent = 0) return approved else: dbg('%d is a !???! character; returning false', indent = 0) return False def _applyFormatting(self): dbg(suspend = 1) dbg('wxMaskedEditMixin::_applyFormatting', indent = 1) if self._signOk: (text, signpos, right_signpos) = self._getSignedValue() dbg('text: "%s", signpos:' % text, signpos) if not text or text[signpos] not in ('-', '('): self._isNeg = False dbg('no valid sign found; new sign:', self._isNeg) if text and signpos != self._signpos: self._signpos = signpos elif text and self._valid and not (self._isNeg) and text[signpos] in ('-', '('): dbg('setting _isNeg to True') self._isNeg = True dbg('self._isNeg:', self._isNeg) if self._signOk and self._isNeg: fc = self._signedForegroundColour else: fc = self._foregroundColour if hasattr(fc, '_name'): c = fc._name else: c = fc dbg('setting foreground to', c) self.SetForegroundColour(fc) if self._valid: dbg('valid') if self.IsEmpty(): bc = self._emptyBackgroundColour else: bc = self._validBackgroundColour else: dbg('invalid') bc = self._invalidBackgroundColour if hasattr(bc, '_name'): c = bc._name else: c = bc dbg('setting background to', c) self.SetBackgroundColour(bc) self._Refresh() dbg(indent = 0, suspend = 0) def _getAbsValue(self, candidate = None): dbg('wxMaskedEditMixin::_getAbsValue; candidate="%s"' % candidate, indent = 1) if candidate is None: text = self._GetValue() else: text = candidate right_signpos = text.find(')') if self._isInt: if self._ctrl_constraints._alignRight and self._fields[0]._fillChar == ' ': signpos = text.find('-') if signpos == -1: dbg('no - found; searching for (') signpos = text.find('(') elif signpos != -1: dbg('- found at', signpos) if signpos == -1: dbg('signpos still -1') dbg('len(%s) (%d) < len(%s) (%d)?' % (text, len(text), self._mask, self._masklength), len(text) < self._masklength) if len(text) < self._masklength: text = ' ' + text if len(text) < self._masklength: text += ' ' if len(text) > self._masklength and text[-1] in (')', ' '): text = text[:-1] else: dbg('len(%s) (%d), len(%s) (%d)' % (text, len(text), self._mask, self._masklength)) dbg('len(%s) - (len(%s) + 1):' % (text, text.lstrip()), len(text) - (len(text.lstrip()) + 1)) signpos = len(text) - (len(text.lstrip()) + 1) if self._useParens and not text.strip(): signpos -= 1 dbg('signpos:', signpos) if signpos >= 0: text = text[:signpos] + ' ' + text[signpos + 1:] elif self._signOk: signpos = 0 text = self._template[0] + text[1:] else: signpos = -1 if right_signpos != -1: if self._signOk: text = text[:right_signpos] + ' ' + text[right_signpos + 1:] elif len(text) > self._masklength: text = text[:right_signpos] + text[right_signpos + 1:] right_signpos = -1 elif self._useParens and self._signOk: right_signpos = self._masklength - 1 if not (self._ctrl_constraints._alignRight): dbg('not right-aligned') if len(text.strip()) == 0: right_signpos = signpos + 1 elif len(text.strip()) < self._masklength: right_signpos = len(text.rstrip()) dbg('right_signpos:', right_signpos) groupchar = self._fields[0]._groupChar try: value = long(text.replace(groupchar, '').replace('(', '-').replace(')', '').replace(' ', '')) dbg('invalid number', indent = 0) return (None, signpos, right_signpos) else: try: groupchar = self._fields[0]._groupChar value = float(text.replace(groupchar, '').replace(self._decimalChar, '.').replace('(', '-').replace(')', '').replace(' ', '')) dbg('value:', value) except: value = None if value < 0 and value is not None: signpos = text.find('-') if signpos == -1: signpos = text.find('(') text = text[:signpos] + self._template[signpos] + text[signpos + 1:] else: dbg('decimal pos:', self._decimalpos) dbg('text: "%s"' % text) if self._signOk: signpos = self._decimalpos - (len(text[:self._decimalpos].lstrip()) + 1) if text[signpos + 1] in ('-', '('): signpos += 1 else: signpos = -1 dbg('signpos:', signpos) if self._useParens: if self._signOk: right_signpos = self._masklength - 1 text = text[:right_signpos] + ' ' if text[signpos] == '(': text = text[:signpos] + ' ' + text[signpos + 1:] else: right_signpos = text.find(')') if right_signpos != -1: text = text[:-1] right_signpos = -1 if value is None: dbg('invalid number') text = None dbg('abstext = "%s"' % text, 'signpos:', signpos, 'right_signpos:', right_signpos) dbg(indent = 0) return (text, signpos, right_signpos) def _getSignedValue(self, candidate = None): dbg('wxMaskedEditMixin::_getSignedValue; candidate="%s"' % candidate, indent = 1) if candidate is None: text = self._GetValue() else: text = candidate (abstext, signpos, right_signpos) = self._getAbsValue(text) if self._signOk: if abstext is None: dbg(indent = 0) return (abstext, signpos, right_signpos) if self._isNeg or text[signpos] in ('-', '('): if self._useParens: sign = '(' else: sign = '-' else: sign = ' ' if abstext[signpos] not in string.digits: text = abstext[:signpos] + sign + abstext[signpos + 1:] else: text = sign + abstext if self._useParens and text.find('(') != -1: text = text[:right_signpos] + ')' + text[right_signpos + 1:] else: text = abstext dbg('signedtext = "%s"' % text, 'signpos:', signpos, 'right_signpos', right_signpos) dbg(indent = 0) return (text, signpos, right_signpos) def GetPlainValue(self, candidate = None): dbg('wxMaskedEditMixin::GetPlainValue; candidate="%s"' % candidate, indent = 1) if candidate is None: text = self._GetValue() else: text = candidate if self.IsEmpty(): dbg('returned ""', indent = 0) return '' else: plain = '' for idx in range(min(len(self._template), len(text))): if self._mask[idx] in maskchars: plain += text[idx] continue if self._isFloat or self._isInt: dbg('plain so far: "%s"' % plain) plain = plain.replace('(', '-').replace(')', ' ') dbg('plain after sign regularization: "%s"' % plain) if self._signOk and self._isNeg and plain.count('-') == 0: plain = '-' + plain.strip() if self._fields[0]._alignRight: lpad = plain.count(',') plain = ' ' * lpad + plain.replace(',', '') else: plain = plain.replace(',', '') dbg('plain after pad and group:"%s"' % plain) dbg('returned "%s"' % plain.rstrip(), indent = 0) return plain.rstrip() def IsEmpty(self, value = None): if value is None: value = self._GetValue() if value == self._template and not (self._defaultValue): return True elif value == self._template: empty = True for pos in range(len(self._template)): if self._isMaskChar(pos) and value[pos] not in (' ', self._fillChar[pos]): empty = False continue return empty else: return False def IsDefault(self, value = None): if value is None: value = self._GetValue() return value == self._template def IsValid(self, value = None): if value is None: value = self._GetValue() ret = self._CheckValid(value) return ret def _eraseSelection(self, value = None, sel_start = None, sel_to = None): dbg('wxMaskedEditMixin::_eraseSelection', indent = 1) if value is None: value = self._GetValue() if sel_start is None or sel_to is None: (sel_start, sel_to) = self._GetSelection() dbg('value: "%s"' % value) dbg('current sel_start, sel_to:', sel_start, sel_to) newvalue = list(value) for i in range(sel_start, sel_to): if self._signOk and newvalue[i] in ('-', '(', ')'): dbg('found sign (%s) at' % newvalue[i], i) if newvalue[i] == '(': right_signpos = value.find(')') if right_signpos != -1: newvalue[right_signpos] = ' ' elif newvalue[i] == ')': left_signpos = value.find('(') if left_signpos != -1: newvalue[left_signpos] = ' ' newvalue[i] = ' ' continue if self._isMaskChar(i): field = self._FindField(i) if field._padZero: newvalue[i] = '0' else: newvalue[i] = self._template[i] field._padZero value = string.join(newvalue, '') dbg('new value: "%s"' % value) dbg(indent = 0) return value def _insertKey(self, char, pos, sel_start, sel_to, value, allowAutoSelect = False): dbg('wxMaskedEditMixin::_insertKey', "'" + char + "'", pos, sel_start, sel_to, '"%s"' % value, indent = 1) text = self._eraseSelection(value) field = self._FindField(pos) (start, end) = field._extent newtext = '' newpos = pos if pos != sel_start and sel_start == sel_to: sel_start = sel_to = pos dbg('field._insertRight?', field._insertRight) if field._insertRight and (sel_start, sel_to) == field._extent and sel_start == sel_to and sel_start == end and field._allowInsert and sel_start < end and text[sel_start] != field._fillChar: dbg('insertRight') fstr = text[start:end] erasable_chars = [ field._fillChar, ' '] if field._padZero: erasable_chars.append('0') erased = '' if fstr[0] in erasable_chars and self._signOk and field._index == 0 and fstr[0] in ('-', '('): erased = fstr[0] field_sel_start = sel_start - start field_sel_to = sel_to - start dbg('left fstr: "%s"' % fstr[1:field_sel_start]) dbg('right fstr: "%s"' % fstr[field_sel_to:end]) fstr = fstr[1:field_sel_start] + char + fstr[field_sel_to:end] if field._alignRight and sel_start != sel_to: field_len = end - start pos = sel_to dbg('setting pos to:', pos) if field._padZero: fstr = '0' * (field_len - len(fstr)) + fstr else: fstr = fstr.rjust(field_len) dbg('field str: "%s"' % fstr) newtext = text[:start] + fstr + text[end:] if erased in ('-', '(') and self._signOk: newtext = erased + newtext[1:] dbg('newtext: "%s"' % newtext) if self._signOk and field._index == 0: start -= 1 if field._moveOnFieldFull and pos == end and len(fstr.lstrip()) == end - start: newpos = self._findNextEntry(end) else: newpos = pos if not newtext: dbg('not newtext') if newpos != pos: dbg('newpos:', newpos) if self._signOk and self._useParens: old_right_signpos = text.find(')') if field._allowInsert and not (field._insertRight) and sel_to <= end and sel_start >= start: field_len = end - start before = text[start:sel_start] after = text[sel_to:end].strip() new_len = len(before) + len(after) + 1 if new_len < field_len: retained = after + self._template[end - field_len - new_len:end] elif new_len > end - start: retained = after[1:] else: retained = after left = text[0:start] + before right = retained + text[end:] else: left = text[0:pos] right = text[pos + 1:] newtext = left + char + right if self._signOk and self._useParens: left_signpos = newtext.find('(') if left_signpos == -1: right_signpos = newtext.find(')') if right_signpos != -1: newtext = newtext[:right_signpos] + ' ' + newtext[right_signpos + 1:] elif old_right_signpos != -1: right_signpos = newtext.find(')') if right_signpos == -1: if newtext[pos] == ' ': newtext = newtext[:left_signpos] + ' ' + newtext[left_signpos + 1:] elif self._ctrl_constraints._alignRight or self._isFloat: newtext = newtext[:-1] + ')' else: rstripped_text = newtext.rstrip() right_signpos = len(rstripped_text) dbg('old_right_signpos:', old_right_signpos, 'right signpos now:', right_signpos) newtext = newtext[:right_signpos] + ')' + newtext[right_signpos + 1:] if field._insertRight and field._moveOnFieldFull and len(newtext[start:end].strip()) == end - start: newpos = self._findNextEntry(end) dbg('newpos = nextentry =', newpos) else: dbg('pos:', pos, 'newpos:', pos + 1) newpos = pos + 1 if allowAutoSelect: new_select_to = newpos match_field = None match_index = None if field._autoSelect: (match_index, partial_match) = self._autoComplete(1, field._compareChoices, newtext[start:end], compareNoCase = field._compareNoCase, current_index = field._autoCompleteIndex - 1) if match_index is not None and partial_match: matched_str = newtext[start:end] newtext = newtext[:start] + field._choices[match_index] + newtext[end:] new_select_to = end match_field = field if field._insertRight: newpos = end - len(field._choices[match_index].strip()) - len(matched_str.strip()) elif self._ctrl_constraints._autoSelect: (match_index, partial_match) = self._autoComplete(1, self._ctrl_constraints._compareChoices, newtext, self._ctrl_constraints._compareNoCase, current_index = self._ctrl_constraints._autoCompleteIndex - 1) if match_index is not None and partial_match: matched_str = newtext newtext = self._ctrl_constraints._choices[match_index] new_select_to = self._ctrl_constraints._extent[1] match_field = self._ctrl_constraints if self._ctrl_constraints._insertRight: newpos = self._masklength - len(self._ctrl_constraints._choices[match_index].strip()) - len(matched_str.strip()) dbg('newtext: "%s"' % newtext, 'newpos:', newpos, 'new_select_to:', new_select_to) dbg(indent = 0) return (newtext, newpos, new_select_to, match_field, match_index) else: dbg('newtext: "%s"' % newtext, 'newpos:', newpos) dbg(indent = 0) return (newtext, newpos) def _OnFocus(self, event): dbg('wxMaskedEditMixin::_OnFocus') wxCallAfter(self._fixSelection) event.Skip() self.Refresh() def _CheckValid(self, candidate = None): dbg(suspend = 1) dbg('wxMaskedEditMixin::_CheckValid: candidate="%s"' % candidate, indent = 1) oldValid = self._valid if candidate is None: value = self._GetValue() else: value = candidate dbg('value: "%s"' % value) oldvalue = value valid = True if not self.IsDefault(value) and self._isDate: valid = self._validateDate(value) dbg('valid date?', valid) elif not self.IsDefault(value) and self._isTime: valid = self._validateTime(value) dbg('valid time?', valid) elif not self.IsDefault(value) and self._isInt or self._isFloat: valid = self._validateNumeric(value) dbg('valid Number?', valid) if valid: valid = self._validateGeneric(value) dbg('valid value?', valid) dbg('valid?', valid) if not candidate: self._valid = valid self._applyFormatting() if self._valid != oldValid: dbg('validity changed: oldValid =', oldValid, 'newvalid =', self._valid) dbg('oldvalue: "%s"' % oldvalue, 'newvalue: "%s"' % self._GetValue()) dbg(indent = 0, suspend = 0) return valid def _validateGeneric(self, candidate = None): if candidate is None: text = self._GetValue() else: text = candidate valid = True for i in [ -1] + self._field_indices: field = self._fields[i] (start, end) = field._extent slice = text[start:end] valid = field.IsValid(slice) if not valid: break continue return valid def _validateNumeric(self, candidate = None): if candidate is None: value = self._GetValue() else: value = candidate try: groupchar = self._fields[0]._groupChar if self._isFloat: number = float(value.replace(groupchar, '').replace(self._decimalChar, '.').replace('(', '-').replace(')', '')) else: number = long(value.replace(groupchar, '').replace('(', '-').replace(')', '')) if value.strip(): if self._fields[0]._alignRight: require_digit_at = self._fields[0]._extent[1] - 1 else: require_digit_at = self._fields[0]._extent[0] dbg('require_digit_at:', require_digit_at) dbg("value[rda]: '%s'" % value[require_digit_at]) if value[require_digit_at] not in list(string.digits): valid = False return valid dbg('number:', number) if self._ctrl_constraints._hasRange: valid = None if number <= number else number <= self._ctrl_constraints._rangeHigh else: valid = True groupcharpos = value.rfind(groupchar) if groupcharpos != -1: dbg('groupchar found at', groupcharpos) if self._isFloat and groupcharpos > self._decimalpos: dbg('groupchar in fraction; illegal') valid = False elif self._isFloat: integer = value[:self._decimalpos].strip() else: integer = value.strip() dbg("integer:'%s'" % integer) if integer[0] in ('-', '('): integer = integer[1:] if integer[-1] == ')': integer = integer[:-1] parts = integer.split(groupchar) dbg('parts:', parts) for i in range(len(parts)): if i == 0 and abs(int(parts[0])) > 999: dbg('group 0 too long; illegal') valid = False break continue if i > 0 and len(parts[i]) != 3 or ' ' in parts[i]: dbg('group %i (%s) not right size; illegal' % (i, parts[i])) valid = False break continue except ValueError: dbg('value not a valid number') valid = False return valid def _validateDate(self, candidate = None): dbg('wxMaskedEditMixin::_validateDate', indent = 1) if candidate is None: value = self._GetValue() else: value = candidate dbg('value = "%s"' % value) text = self._adjustDate(value, force4digit_year = True) dbg('text =', text) valid = True try: datestr = text[0:self._dateExtent] for i in range(3): field = self._fields[i] (start, end) = field._extent fstr = datestr[start:end] fstr.replace(field._fillChar, ' ') datestr = datestr[:start] + fstr + datestr[end:] (year, month, day) = getDateParts(datestr, self._datestyle) year = int(year) dbg('self._dateExtent:', self._dateExtent) if self._dateExtent == 11: month = charmonths_dict[month.lower()] else: month = int(month) day = int(day) dbg('year, month, day:', year, month, day) except ValueError: dbg('cannot convert string to integer parts') valid = False except KeyError: dbg('cannot convert string to integer month') valid = False if valid: if month > 12: valid = False else: month -= 1 try: dbg('trying to create date from values day=%d, month=%d, year=%d' % (day, month, year)) dateHandler = wxDateTimeFromDMY(day, month, year) dbg('succeeded') dateOk = True except: dbg('cannot convert string to valid date') dateOk = False if not dateOk: valid = False if valid: timeStr = text[self._dateExtent + 1:].strip() if timeStr: dbg('timeStr: "%s"' % timeStr) try: checkTime = dateHandler.ParseTime(timeStr) valid = checkTime == len(timeStr) except: valid = False if not valid: dbg('cannot convert string to valid time') if valid: dbg('valid date') dbg(indent = 0) return valid def _validateTime(self, candidate = None): dbg('wxMaskedEditMixin::_validateTime', indent = 1) if candidate is None: value = self._GetValue().strip() else: value = candidate.strip() dbg('value = "%s"' % value) valid = True dateHandler = wxDateTime_Today() try: checkTime = dateHandler.ParseTime(value) dbg('checkTime:', checkTime, 'len(value)', len(value)) valid = checkTime == len(value) except: valid = False if not valid: dbg('cannot convert string to valid time') if valid: dbg('valid time') dbg(indent = 0) return valid def _OnKillFocus(self, event): dbg('wxMaskedEditMixin::_OnKillFocus', 'isDate=', self._isDate, indent = 1) if self._mask and self._IsEditable(): self._AdjustField(self._GetInsertionPoint()) self._CheckValid() self._LostFocus() event.Skip() dbg(indent = 0) def _fixSelection(self): dbg('wxMaskedEditMixin::_fixSelection', indent = 1) if not (self._mask) or not self._IsEditable(): dbg(indent = 0) return None (sel_start, sel_to) = self._GetSelection() dbg('sel_start, sel_to:', sel_start, sel_to, 'self.IsEmpty()?', self.IsEmpty()) if sel_start == 0 and sel_to >= len(self._mask) and not (self._ctrl_constraints._autoSelect) and self.IsEmpty() or self.IsDefault(): dbg('entire text selected; resetting selection to start of control') self._goHome() field = self._FindField(self._GetInsertionPoint()) (edit_start, edit_end) = field._extent if field._selectOnFieldEntry: self._SetInsertionPoint(edit_start) self._SetSelection(edit_start, edit_end) elif field._insertRight: self._SetInsertionPoint(edit_end) self._SetSelection(edit_end, edit_end) elif self._isFloat or self._isInt: (text, signpos, right_signpos) = self._getAbsValue() if text is None or text == self._template: integer = self._fields[0] (edit_start, edit_end) = integer._extent if integer._selectOnFieldEntry: dbg('select on field entry:') self._SetInsertionPoint(edit_start) self._SetSelection(edit_start, edit_end) elif integer._insertRight: dbg('moving insertion point to end') self._SetInsertionPoint(edit_end) self._SetSelection(edit_end, edit_end) else: dbg('numeric ctrl is empty; start at beginning after sign') self._SetInsertionPoint(signpos + 1) self._SetSelection(signpos + 1, signpos + 1) elif sel_start > self._goEnd(getPosOnly = True): dbg('cursor beyond the end of the user input; go to end of it') self._goEnd() else: dbg('sel_start, sel_to:', sel_start, sel_to, 'self._masklength:', self._masklength) dbg(indent = 0) def _Keypress(self, key): return True def _LostFocus(self): pass def _OnDoubleClick(self, event): pos = self._GetInsertionPoint() field = self._FindField(pos) (start, end) = field._extent self._SetInsertionPoint(start) self._SetSelection(start, end) def _Change(self): return True def _Cut(self): dbg('wxMaskedEditMixin::_Cut', indent = 1) value = self._GetValue() dbg('current value: "%s"' % value) (sel_start, sel_to) = self._GetSelection() dbg('selected text: "%s"' % value[sel_start:sel_to].strip()) do = wxTextDataObject() do.SetText(value[sel_start:sel_to].strip()) wxTheClipboard.Open() wxTheClipboard.SetData(do) wxTheClipboard.Close() if sel_to - sel_start != 0: self._OnErase() dbg(indent = 0) def _getClipboardContents(self): do = wxTextDataObject() wxTheClipboard.Open() success = wxTheClipboard.GetData(do) wxTheClipboard.Close() if not success: return None else: return do.GetText().strip() def _validatePaste(self, paste_text, sel_start, sel_to, raise_on_invalid = False): dbg(suspend = 1) dbg('wxMaskedEditMixin::_validatePaste("%(paste_text)s", %(sel_start)d, %(sel_to)d), raise_on_invalid? %(raise_on_invalid)d' % locals(), indent = 1) select_length = sel_to - sel_start maxlength = select_length dbg('sel_to - sel_start:', maxlength) if maxlength == 0: maxlength = self._masklength - sel_start item = 'control' else: item = 'selection' dbg('maxlength:', maxlength) length_considered = len(paste_text) if length_considered > maxlength: dbg('paste text will not fit into the %s:' % item, indent = 0) 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)) else: raise ValueError('"%s" will not fit into the selection' % paste_text) else: dbg(indent = 0, suspend = 0) return (False, None, None) text = self._template dbg('length_considered:', length_considered) valid_paste = True replacement_text = '' replace_to = sel_start i = 0 while valid_paste and i < length_considered and replace_to < self._masklength: if paste_text[i:] == self._template[replace_to:length_considered]: dbg('remainder paste_text[%d:] (%s) matches template[%d:%d]' % (i, paste_text[i:], replace_to, length_considered)) replacement_text += paste_text[i:] replace_to = i = length_considered continue char = paste_text[i] field = self._FindField(replace_to) if not (field._compareNoCase): if field._forceupper: char = char.upper() elif field._forcelower: char = char.lower() dbg('char:', "'" + char + "'", 'i =', i, 'replace_to =', replace_to) dbg('self._isTemplateChar(%d)?' % replace_to, self._isTemplateChar(replace_to)) if not self._isTemplateChar(replace_to) and self._isCharAllowed(char, replace_to, allowAutoSelect = False, ignoreInsertRight = True): replacement_text += char dbg("not template(%(replace_to)d) and charAllowed('%(char)s',%(replace_to)d)" % locals()) dbg('replacement_text:', '"' + replacement_text + '"') i += 1 replace_to += 1 continue if char == self._template[replace_to] and self._signOk: if i == 0 and char == '-' and self._useParens and char == '(' and i == self._masklength - 1 and self._useParens and char == ')': replacement_text += char dbg("'%(char)s' == template(%(replace_to)d)" % locals()) dbg('replacement_text:', '"' + replacement_text + '"') i += 1 replace_to += 1 continue next_entry = self._findNextEntry(replace_to, adjustInsert = False) if next_entry == replace_to: valid_paste = False continue replacement_text += self._template[replace_to:next_entry] dbg('skipping template; next_entry =', next_entry) dbg('replacement_text:', '"' + replacement_text + '"') replace_to = next_entry 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)) 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)) dbg('valid_paste?', valid_paste) if valid_paste: dbg('replacement_text: "%s"' % replacement_text, 'replace to:', replace_to) dbg(indent = 0, suspend = 0) return (valid_paste, replacement_text, replace_to) def _Paste(self, value = None, raise_on_invalid = False, just_return_value = False): dbg('wxMaskedEditMixin::_Paste (value = "%s")' % value, indent = 1) if value is None: paste_text = self._getClipboardContents() else: paste_text = value if paste_text is not None: dbg('paste text: "%s"' % paste_text) (sel_start, sel_to) = self._GetSelection() dbg('selection:', (sel_start, sel_to)) field = self._FindField(sel_start) (edit_start, edit_end) = field._extent new_pos = None if field._allowInsert and sel_to <= edit_end and sel_start + len(paste_text) < edit_end: new_pos = sel_start + len(paste_text) paste_text = paste_text + self._GetValue()[sel_to:edit_end].rstrip() dbg('paste within insertable field; adjusted paste_text: "%s"' % paste_text, 'end:', edit_end) sel_to = sel_start + len(paste_text) if len(paste_text) > sel_to - sel_start and field._insertRight and sel_start > edit_start and sel_to >= edit_end and not self._GetValue()[edit_start:sel_start].strip(): empty_space = sel_start - edit_start amount_needed = len(paste_text) - sel_to - sel_start if amount_needed <= empty_space: sel_start -= amount_needed dbg('expanded selection to:', (sel_start, sel_to)) if self._signOk: (signedvalue, signpos, right_signpos) = self._getSignedValue() paste_signpos = paste_text.find('-') if paste_signpos == -1: paste_signpos = paste_text.find('(') if paste_signpos != -1 and sel_start <= signpos and field._insertRight and sel_start - len(paste_text) <= signpos: signed = True else: signed = False paste_text = paste_text.replace('-', ' ').replace('(', ' ').replace(')', '') dbg('unsigned paste text: "%s"' % paste_text) else: signed = False if field._insertRight and sel_start == edit_end and sel_start == sel_to: sel_start -= len(paste_text) if sel_start < 0: sel_start = 0 dbg('adjusted selection:', (sel_start, sel_to)) try: (valid_paste, replacement_text, replace_to) = self._validatePaste(paste_text, sel_start, sel_to, raise_on_invalid) except: dbg('exception thrown', indent = 0) raise if not valid_paste: dbg('paste text not legal for the selection or portion of the control following the cursor;') if not wxValidator_IsSilent(): wxBell() dbg(indent = 0) return False text = self._eraseSelection() new_text = text[:sel_start] + replacement_text + text[replace_to:] if new_text: new_text = string.ljust(new_text, self._masklength) if signed: (new_text, signpos, right_signpos) = self._getSignedValue(candidate = new_text) if new_text: if self._useParens: new_text = new_text[:signpos] + '(' + new_text[signpos + 1:right_signpos] + ')' + new_text[right_signpos + 1:] else: new_text = new_text[:signpos] + '-' + new_text[signpos + 1:] if not (self._isNeg): self._isNeg = 1 dbg('new_text:', '"' + new_text + '"') if not just_return_value: if new_text == '': self.ClearValue() else: wxCallAfter(self._SetValue, new_text) if new_pos is None: new_pos = sel_start + len(replacement_text) wxCallAfter(self._SetInsertionPoint, new_pos) else: dbg(indent = 0) return new_text elif just_return_value: dbg(indent = 0) return self._GetValue() dbg(indent = 0) def _Undo(self): dbg('wxMaskedEditMixin::_Undo', indent = 1) value = self._GetValue() prev = self._prevValue dbg('current value: "%s"' % value) dbg('previous value: "%s"' % prev) if prev is None: dbg('no previous value', indent = 0) return None elif value != prev: i = 0 length = len(value) while value[:i] == prev[:i]: i += 1 sel_start = i - 1 if self._signOk: (text, signpos, right_signpos) = self._getSignedValue(candidate = prev) if self._useParens: if prev[signpos] == '(' and prev[right_signpos] == ')': self._isNeg = True else: self._isNeg = False value = value.replace(')', ' ') prev = prev.replace(')', ' ') elif prev[signpos] == '-': self._isNeg = True else: self._isNeg = False sm = difflib.SequenceMatcher(None, a = value, b = prev) (i, j, k) = sm.find_longest_match(sel_start, length, sel_start, length) dbg('i,j,k = ', (i, j, k), 'value[i:i+k] = "%s"' % value[i:i + k], 'prev[j:j+k] = "%s"' % prev[j:j + k]) if k == 0: sel_to = length else: code_5tuples = sm.get_opcodes() for op, i1, i2, j1, j2 in code_5tuples: dbg('%7s value[%d:%d] (%s) prev[%d:%d] (%s)' % (op, i1, i2, value[i1:i2], j1, j2, prev[j1:j2])) diff_found = False for next_op in range(len(code_5tuples) - 1, -1, -1): (op, i1, i2, j1, j2) = code_5tuples[next_op] dbg('value[i1:i2]: "%s"' % value[i1:i2], 'template[i1:i2] "%s"' % self._template[i1:i2]) if op == 'insert' and prev[j1:j2] != self._template[j1:j2]: dbg('insert found: selection =>', (j1, j2)) sel_start = j1 sel_to = j2 diff_found = True break continue if op == 'delete' and value[i1:i2] != self._template[i1:i2]: field = self._FindField(i2) (edit_start, edit_end) = field._extent if field._insertRight and i2 == edit_end: sel_start = i2 sel_to = i2 else: sel_start = i1 sel_to = j1 dbg('delete found: selection =>', (sel_start, sel_to)) diff_found = True break continue if op == 'replace': dbg('replace found: selection =>', (j1, j2)) sel_start = j1 sel_to = j2 diff_found = True break continue if diff_found: for next_op in range(len(code_5tuples)): (op, i1, i2, j1, j2) = code_5tuples[next_op] field = self._FindField(i1) if op == 'equal': continue continue if op == 'replace': dbg('setting sel_start to', i1) sel_start = i1 break continue if op == 'insert' and not value[i1:i2]: dbg('forward %s found' % op) if prev[j1:j2].strip(): dbg('item to insert non-empty; setting sel_start to', j1) sel_start = j1 break elif not (field._insertRight): dbg('setting sel_start to inserted space:', j1) sel_start = j1 break prev[j1:j2].strip() if op == 'delete' and field._insertRight and not value[i1:i2].lstrip(): continue continue if not diff_found: dbg('no insert,delete or replace found (!)') if i == j and j != sel_start: sel_to = sel_start + (j - sel_start) else: sel_to = j if (sel_start, sel_to) != self._prevSelection: dbg('calculated selection', (sel_start, sel_to), "doesn't match previous", self._prevSelection) (prev_sel_start, prev_sel_to) = self._prevSelection field = self._FindField(sel_start) if self._signOk and self._prevValue[sel_start] in ('-', '(', ')') or self._curValue[sel_start] in ('-', '(', ')'): (sel_start, sel_to) = self._prevSelection elif field._groupdigits and self._curValue[sel_start:sel_to] == field._groupChar or self._prevValue[sel_start:sel_to] == field._groupChar: (sel_start, sel_to) = self._prevSelection else: calc_select_len = sel_to - sel_start prev_select_len = prev_sel_to - prev_sel_start dbg('sel_start == prev_sel_start', sel_start == prev_sel_start) dbg('sel_to > prev_sel_to', sel_to > prev_sel_to) if prev_select_len >= calc_select_len: (sel_start, sel_to) = self._prevSelection elif sel_to > prev_sel_to and prev_sel_to < len(self._template) and sel_to == len(self._template): (i, j, k) = sm.find_longest_match(prev_sel_to, length, prev_sel_to, length) dbg('i,j,k = ', (i, j, k), 'value[i:i+k] = "%s"' % value[i:i + k], 'prev[j:j+k] = "%s"' % prev[j:j + k]) if k > 0: sel_to = j elif prev_sel_start == prev_sel_to: calc_select_len = sel_to - sel_start field = self._FindField(prev_sel_start) if field._insertRight: test_sel_start = prev_sel_start test_sel_to = prev_sel_start + calc_select_len else: test_sel_start = prev_sel_start - calc_select_len test_sel_to = prev_sel_start else: (test_sel_start, test_sel_to) = (prev_sel_start, prev_sel_to) dbg('test selection:', (test_sel_start, test_sel_to)) dbg('calc change: "%s"' % self._prevValue[sel_start:sel_to]) dbg('test change: "%s"' % self._prevValue[test_sel_start:test_sel_to]) if sel_start != sel_to and test_sel_to < len(self._template) and self._prevValue[test_sel_start:test_sel_to] == self._prevValue[sel_start:sel_to]: (sel_start, sel_to) = (test_sel_start, test_sel_to) dbg('sel_start, sel_to:', sel_start, sel_to) dbg('previous value: "%s"' % self._prevValue) self._SetValue(self._prevValue) self._SetInsertionPoint(sel_start) self._SetSelection(sel_start, sel_to) else: dbg('no difference between previous value') dbg(indent = 0) def _OnClear(self, event): self.ClearValue() def _OnContextMenu(self, event): dbg('wxMaskedEditMixin::OnContextMenu()', indent = 1) menu = wxMenu() menu.Append(wxID_UNDO, 'Undo', '') menu.AppendSeparator() menu.Append(wxID_CUT, 'Cut', '') menu.Append(wxID_COPY, 'Copy', '') menu.Append(wxID_PASTE, 'Paste', '') menu.Append(wxID_CLEAR, 'Delete', '') menu.AppendSeparator() menu.Append(wxID_SELECTALL, 'Select All', '') EVT_MENU(menu, wxID_UNDO, self._OnCtrl_Z) EVT_MENU(menu, wxID_CUT, self._OnCtrl_X) EVT_MENU(menu, wxID_COPY, self._OnCtrl_C) EVT_MENU(menu, wxID_PASTE, self._OnCtrl_V) EVT_MENU(menu, wxID_CLEAR, self._OnClear) EVT_MENU(menu, wxID_SELECTALL, self._OnCtrl_A) EVT_UPDATE_UI(self, wxID_UNDO, self._UndoUpdateUI) self._contextMenu = menu self.PopupMenu(menu, event.GetPosition()) menu.Destroy() self._contextMenu = None dbg(indent = 0) def _UndoUpdateUI(self, event): if self._prevValue is None or self._prevValue == self._curValue: self._contextMenu.Enable(wxID_UNDO, False) else: self._contextMenu.Enable(wxID_UNDO, True) class wxMaskedTextCtrl(wxTextCtrl, wxMaskedEditMixin): def __init__(self, parent, id = -1, value = '', pos = wxDefaultPosition, size = wxDefaultSize, style = wxTE_PROCESS_TAB, validator = wxDefaultValidator, name = 'maskedTextCtrl', setupEventHandling = True, **kwargs): wxTextCtrl.__init__(self, parent, id, value = '', pos = pos, size = size, style = style, validator = validator, name = name) self.controlInitialized = True wxMaskedEditMixin.__init__(self, name, **kwargs) self._SetInitialValue(value) if setupEventHandling: EVT_SET_FOCUS(self, self._OnFocus) EVT_KILL_FOCUS(self, self._OnKillFocus) EVT_LEFT_DCLICK(self, self._OnDoubleClick) EVT_RIGHT_UP(self, self._OnContextMenu) EVT_KEY_DOWN(self, self._OnKeyDown) EVT_CHAR(self, self._OnChar) EVT_TEXT(self, self.GetId(), self._OnTextChange) def __repr__(self): return '<wxMaskedTextCtrl: %s>' % self.GetValue() def _GetSelection(self): return self.GetSelection() def _SetSelection(self, sel_start, sel_to): return self.SetSelection(sel_start, sel_to) def SetSelection(self, sel_start, sel_to): dbg('wxMaskedTextCtrl::SetSelection(%(sel_start)d, %(sel_to)d)' % locals()) wxTextCtrl.SetSelection(self, sel_start, sel_to) def _GetInsertionPoint(self): return self.GetInsertionPoint() def _SetInsertionPoint(self, pos): self.SetInsertionPoint(pos) def SetInsertionPoint(self, pos): dbg('wxMaskedTextCtrl::SetInsertionPoint(%(pos)d)' % locals()) wxTextCtrl.SetInsertionPoint(self, pos) def _GetValue(self): return self.GetValue() def _SetValue(self, value): dbg('wxMaskedTextCtrl::_SetValue("%(value)s")' % locals(), indent = 1) self._prevSelection = self._GetSelection() self._prevInsertionPoint = self._GetInsertionPoint() wxTextCtrl.SetValue(self, value) dbg(indent = 0) def SetValue(self, value): dbg('wxMaskedTextCtrl::SetValue = "%s"' % value, indent = 1) if not (self._mask): wxTextCtrl.SetValue(self, value) return None self._SetInsertionPoint(0) self._SetSelection(0, self._masklength) if self._signOk and self._useParens: signpos = value.find('-') if signpos != -1: value = value[:signpos] + '(' + value[signpos + 1:].strip() + ')' elif value.find(')') == -1 and len(value) < self._masklength: value += ' ' if len(value) < self._masklength: if (self._isFloat or self._isInt) and self._ctrl_constraints._alignRight: dbg('len(value)', len(value), ' < self._masklength', self._masklength) value = self._template[0:self._masklength - len(value)] + value if self._isFloat and value.find('.') == -1: value = value[1:] dbg('padded value = "%s"' % value) try: value = self._Paste(value, raise_on_invalid = True, just_return_value = True) if self._isFloat: self._isNeg = False value = self._adjustFloat(value) elif self._isInt: self._isNeg = False value = self._adjustInt(value) elif self._isDate and not self.IsValid(value) and self._4digityear: value = self._adjustDate(value, fixcentury = true) except ValueError: if self._isDate and self._4digityear: dateparts = value.split(' ') dateparts[0] = self._adjustDate(dateparts[0], fixcentury = true) value = string.join(dateparts, ' ') dbg('adjusted value: "%s"' % value) value = self._Paste(value, raise_on_invalid = True, just_return_value = True) else: dbg('exception thrown', indent = 0) raise except: self._4digityear self._SetValue(value) wxCallAfter(self._SetInsertionPoint, self._masklength) wxCallAfter(self._SetSelection, self._masklength, self._masklength) dbg(indent = 0) def Clear(self): dbg('wxMaskedTextCtrl::Clear - value reset to default value (template)') if self._mask: self.ClearValue() else: wxTextCtrl.Clear(self) def _Refresh(self): dbg('wxMaskedTextCtrl::_Refresh', indent = 1) wxTextCtrl.Refresh(self) dbg(indent = 0) def Refresh(self): dbg('wxMaskedTextCtrl::Refresh', indent = 1) self._CheckValid() self._Refresh() dbg(indent = 0) def _IsEditable(self): return wxTextCtrl.IsEditable(self) def Cut(self): if self._mask: self._Cut() else: wxTextCtrl.Cut(self) def Paste(self): if self._mask: self._Paste() else: wxTextCtrl.Paste(self, value) def Undo(self): if self._mask: self._Undo() else: wxTextCtrl.Undo(self) def IsModified(self): if not wxTextCtrl.IsModified(self): pass return self.modified def _CalcSize(self, size = None): return self._calcSize(size) class wxMaskedComboBoxSelectEvent(wxPyCommandEvent): def __init__(self, id, selection = 0, object = None): wxPyCommandEvent.__init__(self, wxEVT_COMMAND_COMBOBOX_SELECTED, id) self._wxMaskedComboBoxSelectEvent__selection = selection self.SetEventObject(object) def GetSelection(self): return self._wxMaskedComboBoxSelectEvent__selection class wxMaskedComboBox(wxComboBox, wxMaskedEditMixin): def __init__(self, parent, id = -1, value = '', pos = wxDefaultPosition, size = wxDefaultSize, choices = [], style = wxCB_DROPDOWN, validator = wxDefaultValidator, name = 'maskedComboBox', setupEventHandling = True, **kwargs): self._wxMaskedComboBox__readonly = style & wxCB_READONLY == wxCB_READONLY kwargs['choices'] = choices if not kwargs.has_key('compareNoCase'): kwargs['compareNoCase'] = True wxMaskedEditMixin.__init__(self, name, **kwargs) self._choices = self._ctrl_constraints._choices dbg('self._choices:', self._choices) wxComboBox.__init__(self, parent, id, value = '', pos = pos, size = size, choices = choices, style = style | wxWANTS_CHARS, validator = validator, name = name) self.controlInitialized = True self._setFont() if value: if self._ctrl_constraints._alignRight: value = value.rjust(self._masklength) else: value = value.ljust(self._masklength) if self._wxMaskedComboBox__readonly: self.SetStringSelection(value) else: self._SetInitialValue(value) self._SetKeycodeHandler(WXK_UP, self.OnSelectChoice) self._SetKeycodeHandler(WXK_DOWN, self.OnSelectChoice) if setupEventHandling: EVT_SET_FOCUS(self, self._OnFocus) EVT_KILL_FOCUS(self, self._OnKillFocus) EVT_LEFT_DCLICK(self, self._OnDoubleClick) EVT_RIGHT_UP(self, self._OnContextMenu) EVT_CHAR(self, self._OnChar) EVT_KEY_DOWN(self, self.OnKeyDown) EVT_KEY_DOWN(self, self._OnKeyDown) EVT_TEXT(self, self.GetId(), self._OnTextChange) def __repr__(self): return '<wxMaskedComboBox: %s>' % self.GetValue() def _CalcSize(self, size = None): size = self._calcSize(size) return (size[0] + 20, size[1]) def _GetSelection(self): return self.GetMark() def _SetSelection(self, sel_start, sel_to): return self.SetMark(sel_start, sel_to) def _GetInsertionPoint(self): return self.GetInsertionPoint() def _SetInsertionPoint(self, pos): self.SetInsertionPoint(pos) def _GetValue(self): return self.GetValue() def _SetValue(self, value): if self._ctrl_constraints._alignRight: value = value.rjust(self._masklength) else: value = value.ljust(self._masklength) self._prevSelection = self._GetSelection() self._prevInsertionPoint = self._GetInsertionPoint() wxComboBox.SetValue(self, value) self._CheckValid() def SetValue(self, value): if not (self._mask): wxComboBox.SetValue(value) return None self._SetInsertionPoint(0) self._SetSelection(0, self._masklength) if len(value) < self._masklength: if (self._isFloat or self._isInt) and self._ctrl_constraints._alignRight: value = self._template[0:self._masklength - len(value)] + value dbg('padded value = "%s"' % value) elif self._ctrl_constraints._alignRight: value = value.rjust(self._masklength) else: value = value.ljust(self._masklength) try: value = self._Paste(value, raise_on_invalid = True, just_return_value = True) if self._isFloat: self._isNeg = False value = self._adjustFloat(value) elif self._isInt: self._isNeg = False value = self._adjustInt(value) elif self._isDate and not self.IsValid(value) and self._4digityear: value = self._adjustDate(value, fixcentury = true) except ValueError: if self._isDate and self._4digityear: dateparts = value.split(' ') dateparts[0] = self._adjustDate(dateparts[0], fixcentury = true) value = string.join(dateparts, ' ') dbg('adjusted value: "%s"' % value) value = self._Paste(value, raise_on_invalid = True, just_return_value = True) else: raise except: self._4digityear self._SetValue(value) wxCallAfter(self._SetInsertionPoint, self._masklength) wxCallAfter(self._SetSelection, self._masklength, self._masklength) def _Refresh(self): wxComboBox.Refresh(self) def Refresh(self): self._CheckValid() self._Refresh() def _IsEditable(self): return not (self._wxMaskedComboBox__readonly) def Cut(self): if self._mask: self._Cut() else: wxComboBox.Cut(self) def Paste(self): if self._mask: self._Paste() else: wxComboBox.Paste(self) def Undo(self): if self._mask: self._Undo() else: wxComboBox.Undo() def Append(self, choice, clientData = None): if self._mask: if type(choice) not in (types.StringType, types.UnicodeType): raise TypeError('%s: choices must be a sequence of strings' % str(self._index)) elif not self.IsValid(choice): raise ValueError('%s: "%s" is not a valid value for the control as specified.' % (str(self._index), choice)) if not (self._ctrl_constraints._choices): self._ctrl_constraints._compareChoices = [] self._ctrl_constraints._choices = [] self._hasList = True compareChoice = choice.strip() if self._ctrl_constraints._compareNoCase: compareChoice = compareChoice.lower() if self._ctrl_constraints._alignRight: choice = choice.rjust(self._masklength) else: choice = choice.ljust(self._masklength) if self._ctrl_constraints._fillChar != ' ': choice = choice.replace(' ', self._fillChar) dbg('updated choice:', choice) self._ctrl_constraints._compareChoices.append(compareChoice) self._ctrl_constraints._choices.append(choice) self._choices = self._ctrl_constraints._choices if not self.IsValid(choice) and not self._ctrl_constraints.IsEmpty(choice) and self._ctrl_constraints.IsEmpty(choice) and self._ctrl_constraints._validRequired: raise ValueError('"%s" is not a valid value for the control "%s" as specified.' % (choice, self.name)) wxComboBox.Append(self, choice, clientData) def Clear(self): if self._mask: self._choices = [] self._ctrl_constraints._autoCompleteIndex = -1 if self._ctrl_constraints._choices: self.SetCtrlParameters(choices = []) wxComboBox.Clear(self) def SetCtrlParameters(self, **kwargs): wxMaskedEditMixin.SetCtrlParameters(self, **kwargs) if self.controlInitialized and kwargs.has_key('choices') or self._choices != self._ctrl_constraints._choices: wxComboBox.Clear(self) self._choices = self._ctrl_constraints._choices for choice in self._choices: wxComboBox.Append(self, choice) def GetMark(self): dbg(suspend = 1) dbg('wxMaskedComboBox::GetMark', indent = 1) if self._wxMaskedComboBox__readonly: dbg(indent = 0) return (0, 0) sel_start = sel_to = self.GetInsertionPoint() dbg('current sel_start:', sel_start) value = self.GetValue() dbg('value: "%s"' % value) self._ignoreChange = True wxComboBox.Cut(self) newvalue = self.GetValue() dbg('value after Cut operation:', newvalue) if newvalue != value: dbg('something selected') sel_to = sel_start + len(value) - len(newvalue) wxComboBox.SetValue(self, value) wxComboBox.SetInsertionPoint(self, sel_start) wxComboBox.SetMark(self, sel_start, sel_to) self._ignoreChange = False dbg('computed selection:', sel_start, sel_to, indent = 0, suspend = 0) return (sel_start, sel_to) def SetSelection(self, index): dbg('wxMaskedComboBox::SetSelection(%d)' % index) if self._mask: self._prevValue = self._curValue self._curValue = self._choices[index] self._ctrl_constraints._autoCompleteIndex = index wxComboBox.SetSelection(self, index) def OnKeyDown(self, event): if event.GetKeyCode() in self._nav + self._control: self._OnChar(event) return None else: event.Skip() def OnSelectChoice(self, event): dbg('wxMaskedComboBox::OnSelectChoice', indent = 1) if not (self._mask): event.Skip() return None value = self.GetValue().strip() if self._ctrl_constraints._compareNoCase: value = value.lower() if event.GetKeyCode() == WXK_UP: direction = -1 else: direction = 1 (match_index, partial_match) = self._autoComplete(direction, self._ctrl_constraints._compareChoices, value, self._ctrl_constraints._compareNoCase, current_index = self._ctrl_constraints._autoCompleteIndex) if match_index is not None: dbg('setting selection to', match_index) self._OnAutoSelect(self._ctrl_constraints, match_index = match_index) self._CheckValid() keep_processing = False else: pos = self._adjustPos(self._GetInsertionPoint(), event.GetKeyCode()) field = self._FindField(pos) if self.IsEmpty() or not (field._hasList): dbg('selecting 1st value in list') self._OnAutoSelect(self._ctrl_constraints, match_index = 0) self._CheckValid() keep_processing = False else: dbg(indent = 0) keep_processing = self._OnAutoCompleteField(event) dbg('keep processing?', keep_processing, indent = 0) return keep_processing def _OnAutoSelect(self, field, match_index): dbg('wxMaskedComboBox::OnAutoSelect', field._index, indent = 1) if field == self._ctrl_constraints: self.SetSelection(match_index) dbg('issuing combo selection event') self.GetEventHandler().ProcessEvent(wxMaskedComboBoxSelectEvent(self.GetId(), match_index, self)) self._CheckValid() dbg('field._autoCompleteIndex:', match_index) dbg('self.GetSelection():', self.GetSelection()) dbg(indent = 0) def _OnReturn(self, event): dbg('wxMaskedComboBox::OnReturn', indent = 1) dbg('current value: "%s"' % self.GetValue(), 'current index:', self.GetSelection()) if self.GetSelection() == -1 and self.GetValue().lower().strip() in self._ctrl_constraints._compareChoices: wxCallAfter(self.SetSelection, self._ctrl_constraints._autoCompleteIndex) event.m_keyCode = WXK_TAB event.Skip() dbg(indent = 0) class wxIpAddrCtrl(wxMaskedTextCtrl): def __init__(self, parent, id = -1, value = '', pos = wxDefaultPosition, size = wxDefaultSize, style = wxTE_PROCESS_TAB, validator = wxDefaultValidator, name = 'wxIpAddrCtrl', setupEventHandling = True, **kwargs): if not kwargs.has_key('mask'): kwargs['mask'] = mask = '###.###.###.###' if not kwargs.has_key('formatcodes'): kwargs['formatcodes'] = 'F_Sr<' if not kwargs.has_key('validRegex'): 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}' wxMaskedTextCtrl.__init__(self, parent, id = id, value = value, pos = pos, size = size, style = style, validator = validator, name = name, setupEventHandling = setupEventHandling, **kwargs) field_params = { } field_params['validRegex'] = '( | \\d| \\d |\\d | \\d\\d|\\d\\d |\\d \\d|(1\\d\\d|2[0-4]\\d|25[0-5]))' field_params['formatcodes'] = 'V' if field_params: for i in self._field_indices: self.SetFieldParameters(i, **field_params) self._AddNavKey('.', handler = self.OnDot) self._AddNavKey('>', handler = self.OnDot) def OnDot(self, event): dbg('wxIpAddrCtrl::OnDot', indent = 1) pos = self._adjustPos(self._GetInsertionPoint(), event.GetKeyCode()) oldvalue = self.GetValue() (edit_start, edit_end, slice) = self._FindFieldExtent(pos, getslice = True) if not event.ShiftDown(): if pos > edit_start and pos < edit_end: newvalue = oldvalue[:pos] + ' ' * (edit_end - pos) + oldvalue[edit_end:] self._SetValue(newvalue) self._SetInsertionPoint(pos) dbg(indent = 0) return self._OnChangeField(event) def GetAddress(self): value = wxMaskedTextCtrl.GetValue(self) return value.replace(' ', '') def _OnCtrl_S(self, event): dbg('wxIpAddrCtrl::_OnCtrl_S') if self._demo: print 'value:', self.GetAddress() return False def SetValue(self, value): dbg('wxIpAddrCtrl::SetValue(%s)' % str(value), indent = 1) if type(value) not in (types.StringType, types.UnicodeType): dbg(indent = 0) raise ValueError('%s must be a string', str(value)) bValid = True parts = value.split('.') if len(parts) != 4: bValid = False else: for i in range(4): part = parts[i] if not None if len(part) <= len(part) else len(part) <= 3: bValid = False break continue if part.strip(): try: j = string.atoi(part) if not None if j <= j else j <= 255: bValid = False break else: parts[i] = '%3d' % j bValid = False break continue parts[i] = ' ' if not bValid: dbg(indent = 0) 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)) else: dbg('parts:', parts) value = string.join(parts, '.') wxMaskedTextCtrl.SetValue(self, value) dbg(indent = 0) def movetofloat(origvalue, fmtstring, neg, addseparators = False, sepchar = ',', fillchar = ' '): fmt0 = fmtstring.split('.') fmt1 = fmt0[0] fmt2 = fmt0[1] val = origvalue.split('.')[0].strip() ret = fillchar * (len(fmt1) - len(val)) + val + '.' + '0' * len(fmt2) if neg: ret = '-' + ret[1:] return (ret, len(fmt1)) def isDateType(fmtstring): dateMasks = ('^##/##/####', '^##-##-####', '^##.##.####', '^####/##/##', '^####-##-##', '^####.##.##', '^##/CCC/####', '^##.CCC.####', '^##/##/##$', '^##/##/## ', '^##/CCC/##$', '^##.CCC.## ') reString = '|'.join(dateMasks) filter = re.compile(reString) if re.match(filter, fmtstring): return True return False def isTimeType(fmtstring): reTimeMask = '^##:##(:##)?( (AM|PM))?' filter = re.compile(reTimeMask) if re.match(filter, fmtstring): return True return False def isFloatingPoint(fmtstring): filter = re.compile('[ ]?[#]+\\.[#]+\n') if re.match(filter, fmtstring + '\n'): return True return False def isInteger(fmtstring): filter = re.compile('[#]+\n') if re.match(filter, fmtstring + '\n'): return True return False def getDateParts(dateStr, dateFmt): if len(dateStr) > 11: clip = dateStr[0:11] else: clip = dateStr if clip[-2] not in string.digits: clip = clip[:-1] dateSep = ('/' in clip) * '/' + ('-' in clip) * '-' + ('.' in clip) * '.' slices = clip.split(dateSep) if dateFmt == 'MDY': (y, m, d) = (slices[2], slices[0], slices[1]) elif dateFmt == 'DMY': (y, m, d) = (slices[2], slices[1], slices[0]) elif dateFmt == 'YMD': (y, m, d) = (slices[0], slices[1], slices[2]) else: (y, m, d) = (None, None, None) if not y: return None else: return (y, m, d) def getDateSepChar(dateStr): clip = dateStr[0:10] dateSep = ('/' in clip) * '/' + ('-' in clip) * '-' + ('.' in clip) * '.' return dateSep def makeDate(year, month, day, dateFmt, dateStr): sep = getDateSepChar(dateStr) if dateFmt == 'MDY': return '%s%s%s%s%s' % (month, sep, day, sep, year) elif dateFmt == 'DMY': return '%s%s%s%s%s' % (day, sep, month, sep, year) elif dateFmt == 'YMD': return '%s%s%s%s%s' % (year, sep, month, sep, day) else: return none def getYear(dateStr, dateFmt): parts = getDateParts(dateStr, dateFmt) return parts[0] def getMonth(dateStr, dateFmt): parts = getDateParts(dateStr, dateFmt) return parts[1] def getDay(dateStr, dateFmt): parts = getDateParts(dateStr, dateFmt) return parts[2] class test(wxPySimpleApp): def OnInit(self): RowColSizer = RowColSizer import wxPython.lib.rcsizer self.frame = wxFrame(NULL, -1, 'wxMaskedEditMixin 0.0.7 Demo Page #1', size = (700, 600)) self.panel = wxPanel(self.frame, -1) self.sizer = RowColSizer() self.labels = [] self.editList = [] rowcount = 4 (id, id1) = (wxNewId(), wxNewId()) self.command1 = wxButton(self.panel, id, '&Close') self.command2 = wxButton(self.panel, id1, '&AutoFormats') self.sizer.Add(self.command1, row = 0, col = 0, flag = wxALL, border = 5) self.sizer.Add(self.command2, row = 0, col = 1, colspan = 2, flag = wxALL, border = 5) EVT_BUTTON(self.panel, id, self.onClick) EVT_BUTTON(self.panel, id1, self.onClickPage) self.check1 = wxCheckBox(self.panel, -1, 'Disallow Empty') self.check2 = wxCheckBox(self.panel, -1, 'Highlight Empty') self.sizer.Add(self.check1, row = 0, col = 3, flag = wxALL, border = 5) self.sizer.Add(self.check2, row = 0, col = 4, flag = wxALL, border = 5) EVT_CHECKBOX(self.panel, self.check1.GetId(), self._onCheck1) EVT_CHECKBOX(self.panel, self.check2.GetId(), self._onCheck2) label = 'Press ctrl-s in any field to output the value and plain value. Press ctrl-x to clear and re-set any field.\nNote that all controls have been auto-sized by including F in the format code.\nTry entering nonsensical or partial values in validated fields to see what happens (use ctrl-s to test the valid status).' label2 = '\nNote that the State and Last Name fields are list-limited (Name:Smith,Jones,Williams).' self.label1 = wxStaticText(self.panel, -1, label) self.label2 = wxStaticText(self.panel, -1, 'Description') self.label3 = wxStaticText(self.panel, -1, 'Mask Value') self.label4 = wxStaticText(self.panel, -1, 'Format') self.label5 = wxStaticText(self.panel, -1, 'Reg Expr Val. (opt)') self.label6 = wxStaticText(self.panel, -1, 'wxMaskedEdit Ctrl') self.label7 = wxStaticText(self.panel, -1, label2) self.label7.SetForegroundColour('Blue') self.label1.SetForegroundColour('Blue') self.label2.SetFont(wxFont(9, wxSWISS, wxNORMAL, wxBOLD)) self.label3.SetFont(wxFont(9, wxSWISS, wxNORMAL, wxBOLD)) self.label4.SetFont(wxFont(9, wxSWISS, wxNORMAL, wxBOLD)) self.label5.SetFont(wxFont(9, wxSWISS, wxNORMAL, wxBOLD)) self.label6.SetFont(wxFont(9, wxSWISS, wxNORMAL, wxBOLD)) self.sizer.Add(self.label1, row = 1, col = 0, colspan = 7, flag = wxALL, border = 5) self.sizer.Add(self.label7, row = 2, col = 0, colspan = 7, flag = wxALL, border = 5) self.sizer.Add(self.label2, row = 3, col = 0, flag = wxALL, border = 5) self.sizer.Add(self.label3, row = 3, col = 1, flag = wxALL, border = 5) self.sizer.Add(self.label4, row = 3, col = 2, flag = wxALL, border = 5) self.sizer.Add(self.label5, row = 3, col = 3, flag = wxALL, border = 5) self.sizer.Add(self.label6, row = 3, col = 4, flag = wxALL, border = 5) controls = [ ('Phone No', '(###) ###-#### x:###', '', 'F!^-R', '^\\(\\d\\d\\d\\) \\d\\d\\d-\\d\\d\\d\\d', (), [], ''), ('Last Name Only', 'C{14}', '', 'F {list}', '^[A-Z][a-zA-Z]+', (), ('Smith', 'Jones', 'Williams'), ''), ('Full Name', 'C{14}', '', 'F_', '^[A-Z][a-zA-Z]+ [A-Z][a-zA-Z]+', (), [], ''), ('Social Sec#', '###-##-####', '', 'F', '\\d{3}-\\d{2}-\\d{4}', (), [], ''), ('U.S. Zip+4', '#{5}-#{4}', '', 'F', '\\d{5}-(\\s{4}|\\d{4})', (), [], ''), ('U.S. State (2 char)\n(with default)', 'AA', '', 'F!', '[A-Z]{2}', (), states, 'AZ'), ('Customer No', '\\CAA-###', '', 'F!', 'C[A-Z]{2}-\\d{3}', (), [], ''), ('Date (MDY) + Time\n(with default)', '##/##/#### ##:## AM', 'BCDEFGHIJKLMNOQRSTUVWXYZ', 'DFR!', '', (), [], '03/05/2003 12:00 AM'), ('Invoice Total', '#{9}.##', '', 'F-R,', '', (), [], ''), ('Integer (signed)\n(with default)', '#{6}', '', 'F-R', '', (), [], '0 '), ('Integer (unsigned)\n(with default), 1-399', '######', '', 'F', '', (1, 399), [], '1 '), ('Month selector', 'XXX', '', 'F', '', (), [ 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'], ''), ('fraction selector', '#/##', '', 'F', '^\\d\\/\\d\\d?', (), [ '2/3', '3/4', '1/2', '1/4', '1/8', '1/16', '1/32', '1/64'], '')] for control in controls: self.sizer.Add(wxStaticText(self.panel, -1, control[0]), row = rowcount, col = 0, border = 5, flag = wxALL) self.sizer.Add(wxStaticText(self.panel, -1, control[1]), row = rowcount, col = 1, border = 5, flag = wxALL) self.sizer.Add(wxStaticText(self.panel, -1, control[3]), row = rowcount, col = 2, border = 5, flag = wxALL) self.sizer.Add(wxStaticText(self.panel, -1, control[4][:20]), row = rowcount, col = 3, border = 5, flag = wxALL) if control in controls[:]: newControl = wxMaskedTextCtrl(self.panel, -1, '', mask = control[1], excludeChars = control[2], formatcodes = control[3], includeChars = '', validRegex = control[4], validRange = control[5], choices = control[6], defaultValue = control[7], demo = True) if control[6]: newControl.SetCtrlParameters(choiceRequired = True) else: newControl = wxMaskedComboBox(self.panel, -1, '', choices = control[7], choiceRequired = True, mask = control[1], formatcodes = control[3], excludeChars = control[2], includeChars = '', validRegex = control[4], validRange = control[5], demo = True) self.editList.append(newControl) self.sizer.Add(newControl, row = rowcount, col = 4, flag = wxALL, border = 5) rowcount += 1 self.sizer.AddGrowableCol(4) self.panel.SetSizer(self.sizer) self.panel.SetAutoLayout(1) self.frame.Show(1) self.MainLoop() return True def onClick(self, event): self.frame.Close() def onClickPage(self, event): self.page2 = test2(self.frame, -1, '') self.page2.Show(True) def _onCheck1(self, event): value = event.Checked() if value: for control in self.editList: control.SetCtrlParameters(emptyInvalid = True) control.Refresh() else: for control in self.editList: control.SetCtrlParameters(emptyInvalid = False) control.Refresh() self.panel.Refresh() def _onCheck2(self, event): value = event.Checked() if value: for control in self.editList: control.SetCtrlParameters(emptyBackgroundColour = 'Aquamarine') control.Refresh() else: for control in self.editList: control.SetCtrlParameters(emptyBackgroundColour = 'White') control.Refresh() self.panel.Refresh() class test2(wxFrame): def __init__(self, parent, id, caption): wxFrame.__init__(self, parent, id, 'wxMaskedEdit control 0.0.7 Demo Page #2 -- AutoFormats', size = (550, 600)) RowColSizer = RowColSizer import wxPython.lib.rcsizer self.panel = wxPanel(self, -1) self.sizer = RowColSizer() self.labels = [] self.texts = [] rowcount = 4 label = 'All these controls have been created by passing a single parameter, the AutoFormat code.\nThe class contains an internal dictionary of types and formats (autoformats).\nTo see a great example of validations in action, try entering a bad email address, then tab out.' self.label1 = wxStaticText(self.panel, -1, label) self.label2 = wxStaticText(self.panel, -1, 'Description') self.label3 = wxStaticText(self.panel, -1, 'AutoFormat Code') self.label4 = wxStaticText(self.panel, -1, 'wxMaskedEdit Control') self.label1.SetForegroundColour('Blue') self.label2.SetFont(wxFont(9, wxSWISS, wxNORMAL, wxBOLD)) self.label3.SetFont(wxFont(9, wxSWISS, wxNORMAL, wxBOLD)) self.label4.SetFont(wxFont(9, wxSWISS, wxNORMAL, wxBOLD)) self.sizer.Add(self.label1, row = 1, col = 0, colspan = 3, flag = wxALL, border = 5) self.sizer.Add(self.label2, row = 3, col = 0, flag = wxALL, border = 5) self.sizer.Add(self.label3, row = 3, col = 1, flag = wxALL, border = 5) self.sizer.Add(self.label4, row = 3, col = 2, flag = wxALL, border = 5) (id, id1) = (wxNewId(), wxNewId()) self.command1 = wxButton(self.panel, id, '&Close') self.command2 = wxButton(self.panel, id1, '&Print Formats') EVT_BUTTON(self.panel, id, self.onClick) self.panel.SetDefaultItem(self.command1) EVT_BUTTON(self.panel, id1, self.onClickPrint) controls = [ ('Phone No', 'USPHONEFULLEXT'), ('US Date + Time', 'USDATETIMEMMDDYYYY/HHMM'), ('US Date MMDDYYYY', 'USDATEMMDDYYYY/'), ('Time (with seconds)', 'TIMEHHMMSS'), ('Military Time\n(without seconds)', 'MILTIMEHHMM'), ('Social Sec#', 'USSOCIALSEC'), ('Credit Card', 'CREDITCARD'), ('Expiration MM/YY', 'EXPDATEMMYY'), ('Percentage', 'PERCENT'), ("Person's Age", 'AGE'), ('US Zip Code', 'USZIP'), ('US Zip+4', 'USZIPPLUS4'), ('Email Address', 'EMAIL'), ('IP Address', '(derived control wxIpAddrCtrl)')] for control in controls: self.sizer.Add(wxStaticText(self.panel, -1, control[0]), row = rowcount, col = 0, border = 5, flag = wxALL) self.sizer.Add(wxStaticText(self.panel, -1, control[1]), row = rowcount, col = 1, border = 5, flag = wxALL) if control in controls[:-1]: self.sizer.Add(wxMaskedTextCtrl(self.panel, -1, '', autoformat = control[1], demo = True), row = rowcount, col = 2, flag = wxALL, border = 5) else: self.sizer.Add(wxIpAddrCtrl(self.panel, -1, '', demo = True), row = rowcount, col = 2, flag = wxALL, border = 5) rowcount += 1 self.sizer.Add(self.command1, row = 0, col = 0, flag = wxALL, border = 5) self.sizer.Add(self.command2, row = 0, col = 1, flag = wxALL, border = 5) self.sizer.AddGrowableCol(3) self.panel.SetSizer(self.sizer) self.panel.SetAutoLayout(1) def onClick(self, event): self.Close() def onClickPrint(self, event): for format in masktags.keys(): sep = '+------------------------+' print '%s\n%s \n Mask: %s \n RE Validation string: %s\n' % (sep, format, masktags[format]['mask'], masktags[format]['validRegex']) if __name__ == '__main__': app = test() i = 1