diff --git a/wxPython/demo/MaskedNumCtrl.py b/wxPython/demo/MaskedNumCtrl.py index 68dbb26687..24f6e15a91 100644 --- a/wxPython/demo/MaskedNumCtrl.py +++ b/wxPython/demo/MaskedNumCtrl.py @@ -4,6 +4,7 @@ import sys import traceback import wx +import wx.lib.maskededit as me import wx.lib.maskednumctrl as mnum #---------------------------------------------------------------------- @@ -31,13 +32,13 @@ The controls at the top reconfigure the resulting control at the bottom. ) groupcharlabel = wx.StaticText( panel,-1, "Grouping char:" ) - self.groupchar = mnum.MaskedTextCtrl( + self.groupchar = me.MaskedTextCtrl( panel, -1, value=',', mask='&', excludeChars = '-()', formatcodes='F', emptyInvalid=True, validRequired=True ) decimalcharlabel = wx.StaticText( panel,-1, "Decimal char:" ) - self.decimalchar = mnum.MaskedTextCtrl( + self.decimalchar = me.MaskedTextCtrl( panel, -1, value='.', mask='&', excludeChars = '-()', formatcodes='F', emptyInvalid=True, validRequired=True ) diff --git a/wxPython/wx/lib/maskededit.py b/wxPython/wx/lib/maskededit.py index 39d3a17d10..bdc463543e 100644 --- a/wxPython/wx/lib/maskededit.py +++ b/wxPython/wx/lib/maskededit.py @@ -3,15 +3,12 @@ # Authors: Jeff Childers, Will Sadkin # Email: jchilders_98@yahoo.com, wsadkin@nameconnector.com # Created: 02/11/2003 -# Copyright: (c) 2003 by Jeff Childers, 2003 +# Copyright: (c) 2003 by Jeff Childers, Will Sadkin, 2003 # Portions: (c) 2002 by Will Sadkin, 2002-2003 # RCS-ID: $Id$ -# License: wxWindows license +# License: wxWidgets license #---------------------------------------------------------------------------- # NOTE: -# This was written way it is because of the lack of masked edit controls -# in wxWindows/wxPython. -# # MaskedEdit controls are based on a suggestion made on [wxPython-Users] by # Jason Hihn, and borrows liberally from Will Sadkin's original masked edit # control for time entry, TimeCtrl (which is now rewritten using this @@ -433,6 +430,11 @@ decimalChar= ctl.GetChoiceRequired() ctl.GetFormatcodes() + Note: After any change in parameters, the choices for the + control are reevaluated to ensure that they are still legal. If you + have large choice lists, it is therefore more efficient to set parameters + before setting the choices available. + .SetFieldParameters(field_index, **kwargs) This function allows you to specify change individual field parameters after construction. (Indices are 0-based.) @@ -446,7 +448,8 @@ The control detects certain common constructions. In order to use the signed fea decimal point. Without a decimal (e.g. '######', the control will treat it as an integer value. With a decimal (e.g. '###.##'), the control will act as a floating point control (i.e. press decimal to 'tab' to the decimal position). Pressing decimal in the -integer control truncates the value. +integer control truncates the value. However, for a true numeric control, +MaskedNumCtrl provides all this, and true numeric input/output support as well. Check your controls by calling each control's .IsValid() function and the @@ -700,6 +703,34 @@ Event Handling _OnHome(event) _OnEnd(event) + The following routine provides a hook back to any class derivations, so that + they can react to parameter changes before any value is set/reset as a result of + those changes. (eg. MaskedComboBox needs to detect when the choices list is + modified, either implicitly or explicitly, so it can reset the base control + to have the appropriate choice list *before* the initial value is reset to match.) + + _OnCtrlParametersChanged() + +Accessor Functions +------------------ + For convenience, each class derived from MaskedEditMixin should + define an accessors mixin, so that it exposes only those parameters + that make sense for the derivation. This is done with an intermediate + level of inheritance, ie: + + class BaseMaskedTextCtrl( TextCtrl, MaskedEditMixin ): + + class MaskedTextCtrl( BaseMaskedTextCtrl, MaskedEditAccessorsMixin ): + class MaskedNumCtrl( BaseMaskedTextCtrl, MaskedNumCtrlAccessorsMixin ): + class IpAddrCtrl( BaseMaskedTextCtrl, IpAddrCtrlAccessorsMixin ): + class TimeCtrl( BaseMaskedTextCtrl, TimeCtrlAccessorsMixin ): + + etc. + + Each accessors mixin defines Get/Set functions for the base class parameters + that are appropriate for that derivation. + This allows the base classes to be "more generic," exposing the widest + set of options, while not requiring derived classes to be so general. """ import copy @@ -715,7 +746,7 @@ import wx from wx.tools.dbg import Logger dbg = Logger() -dbg(enable=0) +##dbg(enable=0) ## ---------- ---------- ---------- ---------- ---------- ---------- ---------- @@ -847,17 +878,17 @@ masktags = { 'validRegex': '^' + months + '-' + days + '-' + '\d{4} ' + hours + ':' + minutes + ':' + seconds + ' (A|P)M', 'description': "US Date + Time\n(w/hypens)" }, - "USDATEMILTIMEMMDDYYYY/HHMMSS": { + "USDATE24HRTIMEMMDDYYYY/HHMMSS": { 'mask': "##/##/#### ##:##:##", 'formatcodes': 'DF', 'validRegex': '^' + months + '/' + days + '/' + '\d{4} ' + milhours + ':' + minutes + ':' + seconds, - 'description': "US Date + Military Time" + 'description': "US Date + 24Hr (Military) Time" }, - "USDATEMILTIMEMMDDYYYY-HHMMSS": { + "USDATE24HRTIMEMMDDYYYY-HHMMSS": { 'mask': "##-##-#### ##:##:##", 'formatcodes': 'DF', 'validRegex': '^' + months + '-' + days + '-' + '\d{4} ' + milhours + ':' + minutes + ':' + seconds, - 'description': "US Date + Military Time\n(w/hypens)" + 'description': "US Date + 24Hr Time\n(w/hypens)" }, "USDATETIMEMMDDYYYY/HHMM": { 'mask': "##/##/#### ##:## AM", @@ -866,11 +897,11 @@ masktags = { 'validRegex': '^' + months + '/' + days + '/' + '\d{4} ' + hours + ':' + minutes + ' (A|P)M', 'description': "US Date + Time\n(without seconds)" }, - "USDATEMILTIMEMMDDYYYY/HHMM": { + "USDATE24HRTIMEMMDDYYYY/HHMM": { 'mask': "##/##/#### ##:##", 'formatcodes': 'DF', 'validRegex': '^' + months + '/' + days + '/' + '\d{4} ' + milhours + ':' + minutes, - 'description': "US Date + Military Time\n(without seconds)" + 'description': "US Date + 24Hr Time\n(without seconds)" }, "USDATETIMEMMDDYYYY-HHMM": { 'mask': "##-##-#### ##:## AM", @@ -879,11 +910,11 @@ masktags = { 'validRegex': '^' + months + '-' + days + '-' + '\d{4} ' + hours + ':' + minutes + ' (A|P)M', 'description': "US Date + Time\n(w/hypens and w/o secs)" }, - "USDATEMILTIMEMMDDYYYY-HHMM": { + "USDATE24HRTIMEMMDDYYYY-HHMM": { 'mask': "##-##-#### ##:##", 'formatcodes': 'DF', 'validRegex': '^' + months + '-' + days + '-' + '\d{4} ' + milhours + ':' + minutes, - 'description': "US Date + Military Time\n(w/hyphens and w/o seconds)" + 'description': "US Date + 24Hr Time\n(w/hyphens and w/o seconds)" }, "USDATEMMDDYYYY/": { 'mask': "##/##/####", @@ -999,52 +1030,52 @@ masktags = { 'description': "DD.MM.YYYY HH:MM" }, - "EUDATEMILTIMEYYYYMMDD/HHMMSS": { + "EUDATE24HRTIMEYYYYMMDD/HHMMSS": { 'mask': "####/##/## ##:##:##", 'formatcodes': 'DF', 'validRegex': '^' + '\d{4}'+ '/' + months + '/' + days + ' ' + milhours + ':' + minutes + ':' + seconds, - 'description': "YYYY/MM/DD Mil. Time" + 'description': "YYYY/MM/DD 24Hr Time" }, - "EUDATEMILTIMEYYYYMMDD.HHMMSS": { + "EUDATE24HRTIMEYYYYMMDD.HHMMSS": { 'mask': "####.##.## ##:##:##", 'formatcodes': 'DF', 'validRegex': '^' + '\d{4}'+ '.' + months + '.' + days + ' ' + milhours + ':' + minutes + ':' + seconds, - 'description': "YYYY.MM.DD Mil. Time" + 'description': "YYYY.MM.DD 24Hr Time" }, - "EUDATEMILTIMEDDMMYYYY/HHMMSS": { + "EUDATE24HRTIMEDDMMYYYY/HHMMSS": { 'mask': "##/##/#### ##:##:##", 'formatcodes': 'DF', 'validRegex': '^' + days + '/' + months + '/' + '\d{4} ' + milhours + ':' + minutes + ':' + seconds, - 'description': "DD/MM/YYYY Mil. Time" + 'description': "DD/MM/YYYY 24Hr Time" }, - "EUDATEMILTIMEDDMMYYYY.HHMMSS": { + "EUDATE24HRTIMEDDMMYYYY.HHMMSS": { 'mask': "##.##.#### ##:##:##", 'formatcodes': 'DF', 'validRegex': '^' + days + '.' + months + '.' + '\d{4} ' + milhours + ':' + minutes + ':' + seconds, - 'description': "DD.MM.YYYY Mil. Time" + 'description': "DD.MM.YYYY 24Hr Time" }, - "EUDATEMILTIMEYYYYMMDD/HHMM": { + "EUDATE24HRTIMEYYYYMMDD/HHMM": { 'mask': "####/##/## ##:##", 'formatcodes': 'DF','validRegex': '^' + '\d{4}'+ '/' + months + '/' + days + ' ' + milhours + ':' + minutes, - 'description': "YYYY/MM/DD Mil. Time\n(w/o seconds)" + 'description': "YYYY/MM/DD 24Hr Time\n(w/o seconds)" }, - "EUDATEMILTIMEYYYYMMDD.HHMM": { + "EUDATE24HRTIMEYYYYMMDD.HHMM": { 'mask': "####.##.## ##:##", 'formatcodes': 'DF', 'validRegex': '^' + '\d{4}'+ '.' + months + '.' + days + ' ' + milhours + ':' + minutes, - 'description': "YYYY.MM.DD Mil. Time\n(w/o seconds)" + 'description': "YYYY.MM.DD 24Hr Time\n(w/o seconds)" }, - "EUDATEMILTIMEDDMMYYYY/HHMM": { + "EUDATE24HRTIMEDDMMYYYY/HHMM": { 'mask': "##/##/#### ##:##", 'formatcodes': 'DF', 'validRegex': '^' + days + '/' + months + '/' + '\d{4} ' + milhours + ':' + minutes, - 'description': "DD/MM/YYYY Mil. Time\n(w/o seconds)" + 'description': "DD/MM/YYYY 24Hr Time\n(w/o seconds)" }, - "EUDATEMILTIMEDDMMYYYY.HHMM": { + "EUDATE24HRTIMEDDMMYYYY.HHMM": { 'mask': "##.##.#### ##:##", 'formatcodes': 'DF', 'validRegex': '^' + days + '.' + months + '.' + '\d{4} ' + milhours + ':' + minutes, - 'description': "DD.MM.YYYY Mil. Time\n(w/o seconds)" + 'description': "DD.MM.YYYY 24Hr Time\n(w/o seconds)" }, "TIMEHHMMSS": { @@ -1061,17 +1092,17 @@ masktags = { 'validRegex': '^' + hours + ':' + minutes + ' (A|P)M', 'description': "HH:MM (A|P)M\n(see TimeCtrl)" }, - "MILTIMEHHMMSS": { + "24HRTIMEHHMMSS": { 'mask': "##:##:##", 'formatcodes': 'TF', 'validRegex': '^' + milhours + ':' + minutes + ':' + seconds, - 'description': "Military HH:MM:SS\n(see TimeCtrl)" + 'description': "24Hr HH:MM:SS\n(see TimeCtrl)" }, - "MILTIMEHHMM": { + "24HRTIMEHHMM": { 'mask': "##:##", 'formatcodes': 'TF', 'validRegex': '^' + milhours + ':' + minutes, - 'description': "Military HH:MM\n(see TimeCtrl)" + 'description': "24Hr HH:MM\n(see TimeCtrl)" }, "USSOCIALSEC": { 'mask': "###-##-####", @@ -1174,11 +1205,11 @@ class Field: This is the "constructor" for setting up parameters for fields. a field_index of -1 is used to indicate "the entire control." """ -## dbg('Field::Field', indent=1) +#### dbg('Field::Field', indent=1) # Validate legitimate set of parameters: for key in kwargs.keys(): if key not in Field.valid_params.keys(): -## dbg(indent=0) +#### dbg(indent=0) raise TypeError('invalid parameter "%s"' % (key)) # Set defaults for each parameter for this instance, and fully @@ -1192,7 +1223,7 @@ class Field: self._SetParameters(**kwargs) self._ValidateParameters(**kwargs) -## dbg(indent=0) +#### dbg(indent=0) def _SetParameters(self, **kwargs): @@ -1200,19 +1231,21 @@ class Field: This function can be used to set individual or multiple parameters for a masked edit field parameter after construction. """ - dbg(suspend=1) - dbg('maskededit.Field::_SetParameters', indent=1) +## dbg(suspend=1) +## dbg('maskededit.Field::_SetParameters', indent=1) # Validate keyword arguments: for key in kwargs.keys(): if key not in Field.valid_params.keys(): - dbg(indent=0, suspend=0) +## dbg(indent=0, suspend=0) raise AttributeError('invalid keyword argument "%s"' % key) if self._index is not None: dbg('field index:', self._index) - dbg('parameters:', indent=1) +## dbg('parameters:', indent=1) for key, value in kwargs.items(): - dbg('%s:' % key, value) - dbg(indent=0) +## dbg('%s:' % key, value) + pass +## dbg(indent=0) + old_fillChar = self._fillChar # store so we can change choice lists accordingly if it changes @@ -1246,13 +1279,13 @@ class Field: if kwargs.has_key('fillChar'): self._old_fillChar = old_fillChar -## dbg("self._old_fillChar: '%s'" % self._old_fillChar) +#### dbg("self._old_fillChar: '%s'" % self._old_fillChar) if kwargs.has_key('mask') or kwargs.has_key('validRegex'): # (set/changed) self._isInt = isInteger(self._mask) - dbg('isInt?', self._isInt, 'self._mask:"%s"' % self._mask) +## dbg('isInt?', self._isInt, 'self._mask:"%s"' % self._mask) - dbg(indent=0, suspend=0) +## dbg(indent=0, suspend=0) def _ValidateParameters(self, **kwargs): @@ -1260,18 +1293,18 @@ class Field: This function can be used to validate individual or multiple parameters for a masked edit field parameter after construction. """ - dbg(suspend=1) - dbg('maskededit.Field::_ValidateParameters', indent=1) +## dbg(suspend=1) +## dbg('maskededit.Field::_ValidateParameters', indent=1) if self._index is not None: dbg('field index:', self._index) -## dbg('parameters:', indent=1) +#### dbg('parameters:', indent=1) ## for key, value in kwargs.items(): -## dbg('%s:' % key, value) -## dbg(indent=0) -## dbg("self._old_fillChar: '%s'" % self._old_fillChar) +#### dbg('%s:' % key, value) +#### dbg(indent=0) +#### dbg("self._old_fillChar: '%s'" % self._old_fillChar) # Verify proper numeric format params: if self._groupdigits and self._groupChar == self._decimalChar: - dbg(indent=0, suspend=0) +## dbg(indent=0, suspend=0) raise AttributeError("groupChar '%s' cannot be the same as decimalChar '%s'" % (self._groupChar, self._decimalChar)) @@ -1295,7 +1328,7 @@ class Field: else: self._filter = re.compile(self._validRegex) except: - dbg(indent=0, suspend=0) +## dbg(indent=0, suspend=0) raise TypeError('%s: validRegex "%s" not a legal regular expression' % (str(self._index), self._validRegex)) else: self._filter = None @@ -1306,7 +1339,7 @@ class Field: self._rangeLow = 0 if self._validRange: if type(self._validRange) != types.TupleType or len( self._validRange )!= 2 or self._validRange[0] > self._validRange[1]: - dbg(indent=0, suspend=0) +## 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)) ) @@ -1317,16 +1350,16 @@ class Field: if kwargs.has_key('choices') or (len(self._choices) and len(self._choices[0]) != len(self._mask)): # (set/changed) self._hasList = False if self._choices and type(self._choices) not in (types.TupleType, types.ListType): - dbg(indent=0, suspend=0) +## 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) +## dbg(indent=0, suspend=0) raise TypeError('%s: choices must be a sequence of strings' % str(self._index)) length = len(self._mask) - dbg('len(%s)' % self._mask, length, 'len(self._choices):', len(self._choices), 'length:', length, 'self._alignRight?', self._alignRight) +## dbg('len(%s)' % self._mask, length, 'len(self._choices):', len(self._choices), 'length:', length, 'self._alignRight?', self._alignRight) if len(self._choices) and length: if len(self._choices[0]) > length: # changed mask without respecifying choices; readjust the width as appropriate: @@ -1335,7 +1368,7 @@ class Field: self._choices = [choice.rjust( length ) for choice in self._choices] else: self._choices = [choice.ljust( length ) for choice in self._choices] - dbg('aligned choices:', self._choices) +## dbg('aligned choices:', self._choices) if hasattr(self, '_template'): # Verify each choice specified is valid: @@ -1344,28 +1377,28 @@ class Field: # allow empty values even if invalid, (just colored differently) continue if not self.IsValid(choice): - dbg(indent=0, suspend=0) +## dbg(indent=0, suspend=0) raise ValueError('%s: "%s" is not a valid value for the control as specified.' % (str(self._index), choice)) self._hasList = True -## dbg("kwargs.has_key('fillChar')?", kwargs.has_key('fillChar'), "len(self._choices) > 0?", len(self._choices) > 0) -## dbg("self._old_fillChar:'%s'" % self._old_fillChar, "self._fillChar: '%s'" % self._fillChar) +#### dbg("kwargs.has_key('fillChar')?", kwargs.has_key('fillChar'), "len(self._choices) > 0?", len(self._choices) > 0) +#### dbg("self._old_fillChar:'%s'" % self._old_fillChar, "self._fillChar: '%s'" % self._fillChar) if kwargs.has_key('fillChar') and len(self._choices) > 0: if kwargs['fillChar'] != ' ': self._choices = [choice.replace(' ', self._fillChar) for choice in self._choices] else: self._choices = [choice.replace(self._old_fillChar, self._fillChar) for choice in self._choices] - dbg('updated choices:', self._choices) +## dbg('updated choices:', self._choices) if kwargs.has_key('autoSelect') and kwargs['autoSelect']: if not self._hasList: - dbg('no list to auto complete; ignoring "autoSelect=True"') +## dbg('no list to auto complete; ignoring "autoSelect=True"') self._autoSelect = False # reset field validity assumption: self._valid = True - dbg(indent=0, suspend=0) +## dbg(indent=0, suspend=0) def _GetParameter(self, paramname): @@ -1383,28 +1416,28 @@ class Field: Indicates whether the specified slice is considered empty for the field. """ - dbg('Field::IsEmpty("%s")' % slice, indent=1) +## dbg('Field::IsEmpty("%s")' % slice, indent=1) if not hasattr(self, '_template'): - dbg(indent=0) +## dbg(indent=0) raise AttributeError('_template') - dbg('self._template: "%s"' % self._template) - dbg('self._defaultValue: "%s"' % str(self._defaultValue)) +## dbg('self._template: "%s"' % self._template) +## dbg('self._defaultValue: "%s"' % str(self._defaultValue)) if slice == self._template and not self._defaultValue: - dbg(indent=0) +## dbg(indent=0) return True elif slice == self._template: empty = True for pos in range(len(self._template)): -## dbg('slice[%(pos)d] != self._fillChar?' %locals(), slice[pos] != self._fillChar[pos]) +#### dbg('slice[%(pos)d] != self._fillChar?' %locals(), slice[pos] != self._fillChar[pos]) if slice[pos] not in (' ', self._fillChar): empty = False break - dbg("IsEmpty? %(empty)d (do all mask chars == fillChar?)" % locals(), indent=0) +## 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) +## dbg("IsEmpty? 0 (slice doesn't match template)", indent=0) return False @@ -1413,23 +1446,23 @@ class Field: Indicates whether the specified slice is considered a valid value for the field. """ - dbg(suspend=1) - dbg('Field[%s]::IsValid("%s")' % (str(self._index), slice), indent=1) +## dbg(suspend=1) +## dbg('Field[%s]::IsValid("%s")' % (str(self._index), slice), indent=1) valid = True # assume true to start if self.IsEmpty(slice): - dbg(indent=0, suspend=0) +## dbg(indent=0, suspend=0) if self._emptyInvalid: return False else: return True elif self._hasList and self._choiceRequired: - dbg("(member of list required)") +## dbg("(member of list required)") # do case-insensitive match on list; strip surrounding whitespace from slice (already done for choices): if self._fillChar != ' ': slice = slice.replace(self._fillChar, ' ') - dbg('updated slice:"%s"' % slice) +## dbg('updated slice:"%s"' % slice) compareStr = slice.strip() if self._compareNoCase: @@ -1437,7 +1470,7 @@ class Field: valid = compareStr in self._compareChoices elif self._hasRange and not self.IsEmpty(slice): - dbg('validating against range') +## dbg('validating against range') try: # allow float as well as int ranges (int comparisons for free.) valid = self._rangeLow <= float(slice) <= self._rangeHigh @@ -1445,22 +1478,22 @@ class Field: valid = False elif self._validRegex and self._filter: - dbg('validating against regex') +## dbg('validating against regex') valid = (re.match( self._filter, slice) is not None) if valid and self._validFunc: - dbg('validating against supplied function') +## dbg('validating against supplied function') valid = self._validFunc(slice) - dbg('valid?', valid, indent=0, suspend=0) +## dbg('valid?', valid, indent=0, suspend=0) return valid def _AdjustField(self, slice): """ 'Fixes' an integer field. Right or left-justifies, as required.""" - dbg('Field::_AdjustField("%s")' % slice, indent=1) +## dbg('Field::_AdjustField("%s")' % slice, indent=1) length = len(self._mask) -## dbg('length(self._mask):', length) -## dbg('self._useParensForNegatives?', self._useParensForNegatives) +#### dbg('length(self._mask):', length) +#### dbg('self._useParensForNegatives?', self._useParensForNegatives) if self._isInt: if self._useParensForNegatives: signpos = slice.find('(') @@ -1475,13 +1508,13 @@ class Field: intStr = string.replace(intStr,self._fillChar,"") # drop extra fillchars intStr = string.replace(intStr,"-","") # drop sign, if any intStr = string.replace(intStr, self._groupChar, "") # lose commas/dots -## dbg('intStr:"%s"' % intStr) +#### dbg('intStr:"%s"' % intStr) start, end = self._extent field_len = end - start if not self._padZero and len(intStr) != field_len and intStr.strip(): intStr = str(long(intStr)) -## dbg('raw int str: "%s"' % intStr) -## dbg('self._groupdigits:', self._groupdigits, 'self._formatcodes:', self._formatcodes) +#### dbg('raw int str: "%s"' % intStr) +#### dbg('self._groupdigits:', self._groupdigits, 'self._formatcodes:', self._formatcodes) if self._groupdigits: new = '' cnt = 1 @@ -1497,8 +1530,8 @@ class Field: intStr = new # else... leave it without the commas... - dbg('padzero?', self._padZero) - dbg('len(intStr):', len(intStr), 'field length:', length) +## 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: # we had a sign before; restore it @@ -1528,7 +1561,7 @@ class Field: slice = slice.ljust( length ) if self._fillChar != ' ': slice = slice.replace(' ', self._fillChar) - dbg('adjusted slice: "%s"' % slice, indent=0) +## dbg('adjusted slice: "%s"' % slice, indent=0) return slice @@ -1645,7 +1678,7 @@ class MaskedEditMixin: for key, value in MaskedEditMixin.valid_ctrl_params.items(): setattr(self, '_' + key, copy.copy(value)) if not kwargs.has_key(key): -## dbg('%s: "%s"' % (key, repr(value))) +#### dbg('%s: "%s"' % (key, repr(value))) kwargs[key] = copy.copy(value) # Create a "field" that holds global parameters for control constraints @@ -1659,12 +1692,12 @@ class MaskedEditMixin: This public function can be used to set individual or multiple masked edit parameters after construction. """ - dbg(suspend=1) - dbg('MaskedEditMixin::SetCtrlParameters', indent=1) -## dbg('kwargs:', indent=1) +## dbg(suspend=1) +## dbg('MaskedEditMixin::SetCtrlParameters', indent=1) +#### dbg('kwargs:', indent=1) ## for key, value in kwargs.items(): -## dbg(key, '=', value) -## dbg(indent=0) +#### dbg(key, '=', value) +#### dbg(indent=0) # Validate keyword arguments: constraint_kwargs = {} @@ -1672,7 +1705,7 @@ class MaskedEditMixin: for key, value in kwargs.items(): key = key.replace('Color', 'Colour') # for b-c, and standard wxPython spelling if key not in MaskedEditMixin.valid_ctrl_params.keys() + Field.valid_params.keys(): - dbg(indent=0, suspend=0) +## dbg(indent=0, suspend=0) raise TypeError('Invalid keyword argument "%s" for control "%s"' % (key, self.name)) elif key in Field.valid_params.keys(): constraint_kwargs[key] = value @@ -1687,8 +1720,12 @@ class MaskedEditMixin: else: autoformat = None + # handle "parochial name" backward compatibility: + if autoformat and autoformat.find('MILTIME') != -1 and autoformat not in masktags.keys(): + autoformat = autoformat.replace('MILTIME', '24HRTIME') + if autoformat != self._autoformat and autoformat in masktags.keys(): - dbg('autoformat:', autoformat) +## dbg('autoformat:', autoformat) self._autoformat = autoformat mask = masktags[self._autoformat]['mask'] # gather rest of any autoformat parameters: @@ -1699,17 +1736,17 @@ class MaskedEditMixin: elif autoformat and not autoformat in masktags.keys(): raise AttributeError('invalid value for autoformat parameter: %s' % repr(autoformat)) else: - dbg('autoformat not selected') +## dbg('autoformat not selected') if kwargs.has_key('mask'): mask = kwargs['mask'] - dbg('mask:', mask) +## dbg('mask:', mask) ## Assign style flags if mask is None: - dbg('preserving previous mask') +## dbg('preserving previous mask') mask = self._previous_mask # preserve previous mask else: - dbg('mask (re)set') +## dbg('mask (re)set') reset_args['reset_mask'] = mask constraint_kwargs['mask'] = mask @@ -1725,25 +1762,25 @@ class MaskedEditMixin: for i in range(len(fields)): field = fields[i] if not isinstance(field, Field): - dbg(indent=0, suspend=0) +## 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) +## 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) +## dbg(indent=0, suspend=0) raise AttributeError('fields parameter must be a list or dictionary; not %s' % repr(fields)) # Assign constraint parameters for entire control: -## dbg('control constraints:', indent=1) +#### dbg('control constraints:', indent=1) ## for key, value in constraint_kwargs.items(): -## dbg('%s:' % key, value) -## dbg(indent=0) +#### dbg('%s:' % key, value) +#### dbg(indent=0) # determine if changing parameters that should affect the entire control: for key in MaskedEditMixin.valid_ctrl_params.keys(): @@ -1770,15 +1807,15 @@ class MaskedEditMixin: raise TypeError('%s not a legal color specification for %s' % (repr(ctrl_kwargs[key]), key)) - dbg('self._retainFieldValidation:', self._retainFieldValidation) +## dbg('self._retainFieldValidation:', self._retainFieldValidation) if not self._retainFieldValidation: # Build dictionary of any changing parameters which should be propagated to the # component fields: for arg in Field.propagating_params: -## dbg('kwargs.has_key(%s)?' % arg, kwargs.has_key(arg)) -## dbg('getattr(self._ctrl_constraints, _%s)?' % arg, getattr(self._ctrl_constraints, '_'+arg)) +#### dbg('kwargs.has_key(%s)?' % arg, kwargs.has_key(arg)) +#### dbg('getattr(self._ctrl_constraints, _%s)?' % arg, getattr(self._ctrl_constraints, '_'+arg)) reset_args[arg] = kwargs.has_key(arg) and kwargs[arg] != getattr(self._ctrl_constraints, '_'+arg) -## dbg('reset_args[%s]?' % arg, reset_args[arg]) +#### dbg('reset_args[%s]?' % arg, reset_args[arg]) # Set the control-level constraints: self._ctrl_constraints._SetParameters(**constraint_kwargs) @@ -1795,7 +1832,7 @@ class MaskedEditMixin: # Validate that all choices for given fields are at least of the # necessary length, and that they all would be valid pastes if pasted # into their respective fields: -## dbg('validating choices') +#### dbg('validating choices') self._validateChoices() @@ -1822,6 +1859,9 @@ class MaskedEditMixin: elif self._autoformat.find('DMMY') != -1: self._datestyle = 'DMY' elif self._autoformat.find('DMMMY') != -1: self._datestyle = 'DMY' + # Give derived controls a chance to react to parameter changes before + # potentially changing current value of the control. + self._OnCtrlParametersChanged() if self.controlInitialized: # Then the base control is available for configuration; @@ -1831,18 +1871,18 @@ class MaskedEditMixin: self._setFont() if reset_args.has_key('reset_mask'): - dbg('reset mask') +## dbg('reset mask') curvalue = self._GetValue() if curvalue.strip(): try: - dbg('attempting to _SetInitialValue(%s)' % self._GetValue()) +## dbg('attempting to _SetInitialValue(%s)' % self._GetValue()) self._SetInitialValue(self._GetValue()) except Exception, e: - dbg('exception caught:', e) - dbg("current value doesn't work; attempting to reset to template") +## dbg('exception caught:', e) +## dbg("current value doesn't work; attempting to reset to template") self._SetInitialValue() else: - dbg('attempting to _SetInitialValue() with template') +## dbg('attempting to _SetInitialValue() with template') self._SetInitialValue() elif kwargs.has_key('useParensForNegatives'): @@ -1856,19 +1896,19 @@ class MaskedEditMixin: if newvalue[-1] in (' ', ')'): newvalue = newvalue[:-1] - dbg('reconfiguring value for parens:"%s"' % newvalue) +## dbg('reconfiguring value for parens:"%s"' % newvalue) self._SetValue(newvalue) if self._prevValue != newvalue: self._prevValue = newvalue # disallow undo of sign type if self._autofit: - dbg('setting client size to:', self._CalcSize()) +## dbg('setting client size to:', self._CalcSize()) self.SetClientSize(self._CalcSize()) # Set value/type-specific formatting self._applyFormatting() - dbg(indent=0, suspend=0) +## dbg(indent=0, suspend=0) def SetMaskParameters(self, **kwargs): """ old name for this function """ @@ -1891,23 +1931,30 @@ class MaskedEditMixin: return self.GetCtrlParameter(paramname) - # ## TRICKY BIT: to avoid a ton of boiler-plate, and to - # ## automate the getter/setter generation for each valid - # ## control parameter so we never forget to add the - # ## functions when adding parameters, this loop - # ## programmatically adds them to the class: - # ## (This makes it easier for Designers like Boa to - # ## deal with masked controls.) - # ## - 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: - # add non-british spellings, for backward-compatibility - 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)) +## This idea worked, but Boa was unable to use this solution... +## def _attachMethod(self, func): +## import new +## setattr(self, func.__name__, new.instancemethod(func, self, self.__class__)) +## +## +## def _DefinePropertyFunctions(exposed_params): +## for param in exposed_params: +## 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)) +## self._attachMethod(locals()['Set%s' % propname]) +## self._attachMethod(locals()['Get%s' % propname]) +## +## if param.find('Colour') != -1: +## # add non-british spellings, for backward-compatibility +## 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)) +## self._attachMethod(locals()['Set%s' % propname]) +## self._attachMethod(locals()['Get%s' % propname]) +## def SetFieldParameters(self, field_index, **kwargs): @@ -2013,7 +2060,7 @@ class MaskedEditMixin: of booleans indicating whether or not a given position in the mask is a mask character or not. """ - dbg('_processMask: mask', mask, indent=1) +## dbg('_processMask: mask', mask, indent=1) # regular expression for parsing c{n} syntax: rex = re.compile('([' +string.join(maskchars,"") + '])\{(\d+)\}') s = mask @@ -2033,8 +2080,8 @@ class MaskedEditMixin: self._signOk = '-' in self._ctrl_constraints._formatcodes and (self._isFloat or self._isInt) self._useParens = self._ctrl_constraints._useParensForNegatives self._isNeg = False -## dbg('self._signOk?', self._signOk, 'self._useParens?', self._useParens) -## dbg('isFloatingPoint(%s)?' % (s), isFloatingPoint(s), +#### dbg('self._signOk?', self._signOk, 'self._useParens?', self._useParens) +#### dbg('isFloatingPoint(%s)?' % (s), isFloatingPoint(s), ## 'ctrl regex:', self._ctrl_constraints._validRegex) if self._signOk and s[0] != ' ': @@ -2061,10 +2108,10 @@ class MaskedEditMixin: s = s[:i] + s[i+1:] # elide the 2nd '\' as well else: # else if special char, mark position accordingly ismasked[i] = s[i] in maskchars -## dbg('ismasked[%d]:' % i, ismasked[i], s) +#### dbg('ismasked[%d]:' % i, ismasked[i], s) i += 1 # increment to next char -## dbg('ismasked:', ismasked) - dbg('new mask: "%s"' % s, indent=0) +#### dbg('ismasked:', ismasked) +## dbg('new mask: "%s"' % s, indent=0) return s, ismasked @@ -2101,7 +2148,7 @@ class MaskedEditMixin: self._fields[1] = Field() self._decimalpos = string.find( self._mask, '.') - dbg('decimal pos =', self._decimalpos) +## dbg('decimal pos =', self._decimalpos) formatcodes = self._fields[0]._GetParameter('formatcodes') if 'R' not in formatcodes: formatcodes += 'R' @@ -2142,18 +2189,18 @@ class MaskedEditMixin: pos = i # figure out field for 1st editable space: while i <= len(self._mask): -## dbg('searching: outer field loop: i = ', i) +#### dbg('searching: outer field loop: i = ', i) if self._isMaskChar(i): -## dbg('1st char is mask char; recording edit_start=', i) +#### dbg('1st char is mask char; recording edit_start=', i) edit_start = i # Skip to end of editable part of current field: while i < len(self._mask) and self._isMaskChar(i): self._lookupField[i] = field_index i += 1 -## dbg('edit_end =', i) +#### dbg('edit_end =', i) edit_end = i self._lookupField[i] = field_index -## dbg('self._fields.has_key(%d)?' % field_index, self._fields.has_key(field_index)) +#### dbg('self._fields.has_key(%d)?' % field_index, self._fields.has_key(field_index)) if not self._fields.has_key(field_index): kwargs = Field.valid_params.copy() kwargs['index'] = field_index @@ -2174,15 +2221,15 @@ class MaskedEditMixin: break # if past end, we're done else: field_index += 1 -## dbg('next field:', field_index) +#### dbg('next field:', field_index) indices = self._fields.keys() indices.sort() self._field_indices = indices[1:] -## dbg('lookupField map:', indent=1) +#### dbg('lookupField map:', indent=1) ## for i in range(len(self._mask)): -## dbg('pos %d:' % i, self._lookupField[i]) -## dbg(indent=0) +#### dbg('pos %d:' % i, self._lookupField[i]) +#### dbg(indent=0) # Verify that all field indices specified are valid for mask: for index in self._fields.keys(): @@ -2203,7 +2250,7 @@ class MaskedEditMixin: for field in self._fields.values(): if field._defaultValue and not reset_default: default_set = True - dbg('default set?', default_set) +## dbg('default set?', default_set) # Determine overall new template for control, and keep track of previous # values, so that current control value can be modified as appropriate: @@ -2225,9 +2272,9 @@ class MaskedEditMixin: field._template = "" for pos in range(len(self._mask)): -## dbg('pos:', pos) +#### dbg('pos:', pos) field = self._FindField(pos) -## dbg('field:', field._index) +#### dbg('field:', field._index) start, end = field._extent if pos == 0 and self._signOk: @@ -2250,32 +2297,32 @@ class MaskedEditMixin: curvalue[pos] = fillChar if not field._defaultValue and not self._ctrl_constraints._defaultValue: -## dbg('no default value') +#### dbg('no default value') self._template += fillChar field._template += fillChar elif field._defaultValue and not reset_default: -## dbg('len(field._defaultValue):', len(field._defaultValue)) -## dbg('pos-start:', pos-start) +#### dbg('len(field._defaultValue):', len(field._defaultValue)) +#### dbg('pos-start:', pos-start) if len(field._defaultValue) > pos-start: -## dbg('field._defaultValue[pos-start]: "%s"' % field._defaultValue[pos-start]) +#### dbg('field._defaultValue[pos-start]: "%s"' % field._defaultValue[pos-start]) self._template += field._defaultValue[pos-start] field._template += field._defaultValue[pos-start] else: -## dbg('field default not long enough; using fillChar') +#### dbg('field default not long enough; using fillChar') self._template += fillChar field._template += fillChar else: if len(self._ctrl_constraints._defaultValue) > pos: -## dbg('using control default') +#### dbg('using control default') self._template += self._ctrl_constraints._defaultValue[pos] field._template += self._ctrl_constraints._defaultValue[pos] else: -## dbg('ctrl default not long enough; using fillChar') +#### dbg('ctrl default not long enough; using fillChar') self._template += fillChar field._template += fillChar -## dbg('field[%d]._template now "%s"' % (field._index, field._template)) -## dbg('self._template now "%s"' % self._template) +#### dbg('field[%d]._template now "%s"' % (field._index, field._template)) +#### dbg('self._template now "%s"' % self._template) else: self._template += self._mask[pos] @@ -2288,9 +2335,9 @@ class MaskedEditMixin: if default_set: self._defaultValue = self._template - dbg('self._defaultValue:', self._defaultValue) +## dbg('self._defaultValue:', self._defaultValue) if not self.IsEmpty(self._defaultValue) and not self.IsValid(self._defaultValue): -## dbg(indent=0) +#### dbg(indent=0) raise ValueError('Default value of "%s" is not a valid value for control "%s"' % (self._defaultValue, self.name)) # if no fillchar change, but old value == old template, replace it: @@ -2301,7 +2348,7 @@ class MaskedEditMixin: self._defaultValue = None if reset_value: - dbg('resetting value to: "%s"' % newvalue) +## dbg('resetting value to: "%s"' % newvalue) pos = self._GetInsertionPoint() sel_start, sel_to = self._GetSelection() self._SetValue(newvalue) @@ -2347,11 +2394,11 @@ class MaskedEditMixin: inherit_args['defaultValue'] = "" # (reset for field) for param in Field.propagating_params: -## dbg('reset_args.has_key(%s)?' % param, reset_args.has_key(param)) -## dbg('reset_args.has_key(%(param)s) and reset_args[%(param)s]?' % locals(), reset_args.has_key(param) and reset_args[param]) +#### dbg('reset_args.has_key(%s)?' % param, reset_args.has_key(param)) +#### dbg('reset_args.has_key(%(param)s) and reset_args[%(param)s]?' % locals(), reset_args.has_key(param) and reset_args[param]) if reset_args.has_key(param): inherit_args[param] = self.GetCtrlParameter(param) -## dbg('inherit_args[%s]' % param, inherit_args[param]) +#### dbg('inherit_args[%s]' % param, inherit_args[param]) if inherit_args: field._SetParameters(**inherit_args) @@ -2368,22 +2415,22 @@ class MaskedEditMixin: 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') +## dbg('skipping (duplicate) choice validation of field 0') continue -## dbg('checking for choices for field', field._index) +#### dbg('checking for choices for field', field._index) start, end = field._extent field_length = end - start -## dbg('start, end, length:', start, end, field_length) +#### dbg('start, end, length:', start, end, field_length) for choice in field._choices: -## dbg('testing "%s"' % choice) +#### dbg('testing "%s"' % choice) valid_paste, ignore, replace_to = self._validatePaste(choice, start, end) if not valid_paste: -## dbg(indent=0) +#### dbg(indent=0) raise ValueError('"%s" could not be entered into field %d of control "%s"' % (choice, index, self.name)) elif replace_to > end: -## dbg(indent=0) +#### dbg(indent=0) raise ValueError('"%s" will not fit into field %d of control "%s"' (choice, index, self.name)) -## dbg(choice, 'valid in field', index) +#### dbg(choice, 'valid in field', index) def _configure(self, mask, **reset_args): @@ -2401,19 +2448,19 @@ class MaskedEditMixin: whole control. """ - dbg(suspend=1) - dbg('MaskedEditMixin::_configure("%s")' % mask, indent=1) +## dbg(suspend=1) +## dbg('MaskedEditMixin::_configure("%s")' % mask, indent=1) # Preprocess specified mask to expand {n} syntax, handle escaped # mask characters, etc and build the resulting positionally keyed # dictionary for which positions are mask vs. template characters: self._mask, self.ismasked = self._processMask(mask) self._masklength = len(self._mask) -## dbg('processed mask:', self._mask) +#### dbg('processed mask:', self._mask) # Preserve original mask specified, for subsequent reprocessing # if parameters change. - dbg('mask: "%s"' % self._mask, 'previous mask: "%s"' % self._previous_mask) +## dbg('mask: "%s"' % self._mask, 'previous mask: "%s"' % self._previous_mask) self._previous_mask = mask # save unexpanded mask for next time # Set expanded mask and extent of field -1 to width of entire control: self._ctrl_constraints._SetParameters(mask = self._mask, extent=(0,self._masklength)) @@ -2422,7 +2469,7 @@ class MaskedEditMixin: # instances as necessary, configure them with those extents, and # build lookup table mapping each position for control to its corresponding # field. -## dbg('calculating field extents') +#### dbg('calculating field extents') self._calcFieldExtents() @@ -2432,13 +2479,13 @@ class MaskedEditMixin: reset_fillchar = reset_args.has_key('fillChar') and reset_args['fillChar'] reset_default = reset_args.has_key('defaultValue') and reset_args['defaultValue'] -## dbg('calculating template') +#### dbg('calculating template') self._calcTemplate(reset_fillchar, reset_default) # Propagate control-level formatting and character constraints to each # field if they don't already have them; if only one field, propagate # control-level validation constraints to field as well: -## dbg('propagating constraints') +#### dbg('propagating constraints') self._propagateConstraints(**reset_args) @@ -2446,10 +2493,10 @@ class MaskedEditMixin: raise AttributeError('groupChar (%s) and decimalChar (%s) must be distinct.' % (self._fields[0]._groupChar, self._decimalChar) ) -## dbg('fields:', indent=1) +#### dbg('fields:', indent=1) ## for i in [-1] + self._field_indices: -## dbg('field %d:' % i, self._fields[i].__dict__) -## dbg(indent=0) +#### dbg('field %d:' % i, self._fields[i].__dict__) +#### dbg(indent=0) # Set up special parameters for numeric control, if appropriate: if self._signOk: @@ -2467,7 +2514,7 @@ class MaskedEditMixin: if self._isFloat or self._isInt: if self.controlInitialized: value = self._GetValue() -## dbg('value: "%s"' % value, 'len(value):', len(value), +#### dbg('value: "%s"' % value, 'len(value):', len(value), ## 'len(self._ctrl_constraints._mask):',len(self._ctrl_constraints._mask)) if len(value) < len(self._ctrl_constraints._mask): newvalue = value @@ -2480,12 +2527,12 @@ class MaskedEditMixin: 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) +## dbg('old value: "%s"' % value) +## dbg('new value: "%s"' % newvalue) try: self._SetValue(newvalue) except Exception, e: - dbg('exception raised:', e, 'resetting to initial value') +## dbg('exception raised:', e, 'resetting to initial value') self._SetInitialValue() elif len(value) > len(self._ctrl_constraints._mask): @@ -2497,27 +2544,27 @@ class MaskedEditMixin: if not self._signOk: newvalue, signpos, right_signpos = self._getSignedValue(newvalue) - dbg('old value: "%s"' % value) - dbg('new value: "%s"' % newvalue) +## dbg('old value: "%s"' % value) +## dbg('new value: "%s"' % newvalue) try: self._SetValue(newvalue) except Exception, e: - dbg('exception raised:', e, 'resetting to initial value') +## dbg('exception raised:', e, 'resetting to initial value') self._SetInitialValue() elif 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) +## dbg('old value: "%s"' % value) +## dbg('new value: "%s"' % newvalue) try: self._SetValue(newvalue) except e: - dbg('exception raised:', e, 'resetting to initial value') +## dbg('exception raised:', e, 'resetting to initial value') self._SetInitialValue() # Replace up/down arrow default handling: # make down act like tab, up act like shift-tab: -## dbg('Registering numeric navigation and control handlers (if not already set)') +#### dbg('Registering numeric navigation and control handlers (if not already set)') if not self._keyhandlers.has_key(wx.WXK_DOWN): self._SetKeycodeHandler(wx.WXK_DOWN, self._OnChangeField) if not self._keyhandlers.has_key(wx.WXK_UP): @@ -2534,7 +2581,7 @@ class MaskedEditMixin: if not self._keyhandlers.has_key(ord(self._fields[0]._groupChar)): self._SetKeyHandler(self._fields[0]._groupChar, self._OnGroupChar) - dbg(indent=0, suspend=0) +## dbg(indent=0, suspend=0) def _SetInitialValue(self, value=""): @@ -2543,7 +2590,7 @@ class MaskedEditMixin: It will also set/reset the font if necessary and apply formatting to the control at this time. """ - dbg('MaskedEditMixin::_SetInitialValue("%s")' % value, indent=1) +## dbg('MaskedEditMixin::_SetInitialValue("%s")' % value, indent=1) if not value: self._prevValue = self._curValue = self._template # don't apply external validation rules in this case, as template may @@ -2551,27 +2598,27 @@ class MaskedEditMixin: try: self._SetValue(self._curValue) # note the use of "raw" ._SetValue()... except Exception, e: - dbg('exception thrown:', e, indent=0) +## dbg('exception thrown:', e, indent=0) raise else: # Otherwise apply validation as appropriate to passed value: -## dbg('value = "%s", length:' % value, len(value)) +#### dbg('value = "%s", length:' % value, len(value)) self._prevValue = self._curValue = value try: self.SetValue(value) # use public (validating) .SetValue() except Exception, e: - dbg('exception thrown:', e, indent=0) +## dbg('exception thrown:', e, indent=0) raise # Set value/type-specific formatting self._applyFormatting() - dbg(indent=0) +## dbg(indent=0) def _calcSize(self, size=None): """ Calculate automatic size if allowed; must be called after the base control is instantiated""" -## dbg('MaskedEditMixin::_calcSize', indent=1) +#### dbg('MaskedEditMixin::_calcSize', indent=1) cont = (size is None or size == wx.DefaultSize) if cont and self._autofit: @@ -2580,29 +2627,29 @@ class MaskedEditMixin: sizing_text += 'M' if wx.Platform == "__WXMAC__": # give it even a little more... sizing_text += 'M' -## dbg('len(sizing_text):', len(sizing_text), 'sizing_text: "%s"' % sizing_text) +#### dbg('len(sizing_text):', len(sizing_text), 'sizing_text: "%s"' % sizing_text) w, h = self.GetTextExtent(sizing_text) size = (w+4, self.GetClientSize().height) -## dbg('size:', size, indent=0) +#### dbg('size:', size, indent=0) return size def _setFont(self): """ Set the control's font typeface -- pass the font name as str.""" -## dbg('MaskedEditMixin::_setFont', indent=1) +#### dbg('MaskedEditMixin::_setFont', indent=1) if not self._useFixedWidthFont: self._font = wx.SystemSettings_GetFont(wx.SYS_DEFAULT_GUI_FONT) else: font = self.GetFont() # get size, weight, etc from current font - # Set to teletype font (guaranteed to be mappable to all wxWindows + # Set to teletype font (guaranteed to be mappable to all wxWidgets # platforms: self._font = wx.Font( font.GetPointSize(), wx.TELETYPE, font.GetStyle(), font.GetWeight(), font.GetUnderlined()) -## dbg('font string: "%s"' % font.GetNativeFontInfo().ToString()) +#### dbg('font string: "%s"' % font.GetNativeFontInfo().ToString()) self.SetFont(self._font) -## dbg(indent=0) +#### dbg(indent=0) def _OnTextChange(self, event): @@ -2616,31 +2663,32 @@ class MaskedEditMixin: EVT_TEXT events for the same change.) """ newvalue = self._GetValue() - dbg('MaskedEditMixin::_OnTextChange: value: "%s"' % newvalue, indent=1) +## dbg('MaskedEditMixin::_OnTextChange: value: "%s"' % newvalue, indent=1) bValid = False if self._ignoreChange: # ie. if an "intermediate text change event" - dbg(indent=0) +## dbg(indent=0) return bValid ##! WS: For some inexplicable reason, every wxTextCtrl.SetValue ## call is generating two (2) EVT_TEXT events. ## This is the only mechanism I can find to mask this problem: if newvalue == self._curValue: - dbg('ignoring bogus text change event', indent=0) +## dbg('ignoring bogus text change event', indent=0) + pass else: - dbg('curvalue: "%s", newvalue: "%s"' % (self._curValue, newvalue)) +## 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') +## dbg('clearing self._isNeg') self._isNeg = False text, self._signpos, self._right_signpos = self._getSignedValue() self._CheckValid() # Recolor control as appropriate - dbg('calling event.Skip()') +## dbg('calling event.Skip()') event.Skip() bValid = True self._prevValue = self._curValue # save for undo self._curValue = newvalue # Save last seen value for next iteration - dbg(indent=0) +## dbg(indent=0) return bValid @@ -2654,7 +2702,7 @@ class MaskedEditMixin: if key in self._nav and event.ControlDown(): # then this is the only place we will likely see these events; # process them now: - dbg('MaskedEditMixin::OnKeyDown: calling _OnChar') +## dbg('MaskedEditMixin::OnKeyDown: calling _OnChar') self._OnChar(event) return # else allow regular EVT_CHAR key processing @@ -2666,25 +2714,25 @@ class MaskedEditMixin: This is the engine of wxMaskedEdit controls. It examines each keystroke, decides if it's allowed, where it should go or what action to take. """ - dbg('MaskedEditMixin::_OnChar', indent=1) +## dbg('MaskedEditMixin::_OnChar', indent=1) # Get keypress value, adjusted by control options (e.g. convert to upper etc) key = event.GetKeyCode() orig_pos = self._GetInsertionPoint() orig_value = self._GetValue() - dbg('keycode = ', key) - dbg('current pos = ', orig_pos) - dbg('current selection = ', self._GetSelection()) +## dbg('keycode = ', key) +## dbg('current pos = ', orig_pos) +## dbg('current selection = ', self._GetSelection()) if not self._Keypress(key): - dbg(indent=0) +## dbg(indent=0) return # If no format string for this control, or the control is marked as "read-only", # skip the rest of the special processing, and just "do the standard thing:" if not self._mask or not self._IsEditable(): event.Skip() - dbg(indent=0) +## dbg(indent=0) return # Process navigation and control keys first, with @@ -2695,77 +2743,77 @@ class MaskedEditMixin: if self._GetValue() != orig_value: self.modified = True if not keep_processing: - dbg(indent=0) +## dbg(indent=0) return self._applyFormatting() - dbg(indent=0) +## dbg(indent=0) return # Else... adjust the position as necessary for next input key, # and determine resulting selection: pos = self._adjustPos( orig_pos, key ) ## get insertion position, adjusted as needed sel_start, sel_to = self._GetSelection() ## check for a range of selected text - dbg("pos, sel_start, sel_to:", pos, sel_start, sel_to) +## dbg("pos, sel_start, sel_to:", pos, sel_start, sel_to) keep_processing = True # Capture user past end of format field if pos > len(self.maskdict): - dbg("field length exceeded:",pos) +## dbg("field length exceeded:",pos) keep_processing = False if keep_processing: if self._isMaskChar(pos): ## Get string of allowed characters for validation okchars = self._getAllowedChars(pos) else: - dbg('Not a valid position: pos = ', pos,"chars=",maskchars) +## dbg('Not a valid position: pos = ', pos,"chars=",maskchars) okchars = "" key = self._adjustKey(pos, key) # apply formatting constraints to key: if self._keyhandlers.has_key(key): # there's an override for default behavior; use override function instead - dbg('using supplied key handler:', self._keyhandlers[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) +## dbg(indent=0) return # else skip default processing, but do final formatting if key < wx.WXK_SPACE or key > 255: - dbg('key < WXK_SPACE or key > 255') +## dbg('key < WXK_SPACE or key > 255') event.Skip() # non alphanumeric keep_processing = False else: field = self._FindField(pos) - dbg("key ='%s'" % chr(key)) +## dbg("key ='%s'" % chr(key)) if chr(key) == ' ': - dbg('okSpaces?', field._okSpaces) - +## dbg('okSpaces?', field._okSpaces) + pass 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") +## dbg("key allowed by mask") # insert key into candidate new value, but don't change control yet: 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) +## 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:') +## 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) +## dbg('adjusted str:', newstr) if self.IsValid(newstr): - dbg("it is!") +## dbg("it is!") keep_processing = True wx.CallAfter(self._SetInsertionPoint, self._decimalpos) if not keep_processing: - dbg("key disallowed by validation") +## dbg("key disallowed by validation") if not wx.Validator_IsSilent() and orig_pos == pos: wx.Bell() @@ -2775,7 +2823,7 @@ class MaskedEditMixin: # special case: adjust date value as necessary: if self._isDate and newstr != self._template: newstr = self._adjustDate(newstr) - dbg('adjusted newstr:', newstr) +## dbg('adjusted newstr:', newstr) if newstr != orig_value: self.modified = True @@ -2791,21 +2839,21 @@ class MaskedEditMixin: wx.CallAfter(self._SetInsertionPoint, newpos) if match_field is not None: - dbg('matched field') +## dbg('matched field') self._OnAutoSelect(match_field, match_index) if new_select_to != newpos: - dbg('queuing selection: (%d, %d)' % (newpos, new_select_to)) +## dbg('queuing selection: (%d, %d)' % (newpos, new_select_to)) wx.CallAfter(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])) +## dbg('queuing selection: (%d, %d)' % (newfield._extent[0], newfield._extent[1])) wx.CallAfter(self._SetSelection, newfield._extent[0], newfield._extent[1]) keep_processing = False elif keep_processing: - dbg('char not allowed') +## dbg('char not allowed') keep_processing = False if (not wx.Validator_IsSilent()) and orig_pos == pos: wx.Bell() @@ -2817,12 +2865,12 @@ class MaskedEditMixin: 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()) +## dbg("moving from %(pos)d to next valid entry: %(next_entry)d" % locals()) wx.CallAfter(self._SetInsertionPoint, next_entry ) if self._isTemplateChar(pos): self._AdjustField(pos) - dbg(indent=0) +## dbg(indent=0) def _FindFieldExtent(self, pos=None, getslice=False, value=None): @@ -2846,8 +2894,7 @@ class MaskedEditMixin: 10, 14 etc. """ - dbg('MaskedEditMixin::_FindFieldExtent(pos=%s, getslice=%s)' % ( - str(pos), str(getslice)) ,indent=1) +## dbg('MaskedEditMixin::_FindFieldExtent(pos=%s, getslice=%s)' % (str(pos), str(getslice)) ,indent=1) field = self._FindField(pos) if not field: @@ -2859,12 +2906,12 @@ class MaskedEditMixin: 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) +## 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) +## dbg('edit_start:', edit_start, 'edit_end:', edit_end) +## dbg(indent=0) return edit_start, edit_end @@ -2876,23 +2923,23 @@ class MaskedEditMixin: when calculating the current field. """ -## dbg('MaskedEditMixin::_FindField(pos=%s)' % str(pos) ,indent=1) +#### dbg('MaskedEditMixin::_FindField(pos=%s)' % str(pos) ,indent=1) 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) +## dbg(indent=0) return None # else... -## dbg(indent=0) +#### dbg(indent=0) return self._fields[self._lookupField[pos]] def ClearValue(self): """ Blanks the current control value by replacing it with the default value.""" - dbg("MaskedEditMixin::ClearValue - value reset to default value (template)") +## dbg("MaskedEditMixin::ClearValue - value reset to default value (template)") self._SetValue( self._template ) self._SetInsertionPoint(0) self.Refresh() @@ -2911,11 +2958,11 @@ class MaskedEditMixin: Makes up-arrow act like shift-tab should; ie. take you to start of previous field. """ - dbg('MaskedEditMixin::_OnUpNumeric', indent=1) +## dbg('MaskedEditMixin::_OnUpNumeric', indent=1) event.m_shiftDown = 1 - dbg('event.ShiftDown()?', event.ShiftDown()) +## dbg('event.ShiftDown()?', event.ShiftDown()) self._OnChangeField(event) - dbg(indent=0) +## dbg(indent=0) def _OnArrow(self, event): @@ -2923,7 +2970,7 @@ class MaskedEditMixin: Used in response to left/right navigation keys; makes these actions skip over mask template chars. """ - dbg("MaskedEditMixin::_OnArrow", indent=1) +## dbg("MaskedEditMixin::_OnArrow", indent=1) pos = self._GetInsertionPoint() keycode = event.GetKeyCode() sel_start, sel_to = self._GetSelection() @@ -2931,12 +2978,12 @@ class MaskedEditMixin: if keycode in (wx.WXK_RIGHT, wx.WXK_DOWN): if( ( not self._isTemplateChar(pos) and pos+1 > entry_end) or ( self._isTemplateChar(pos) and pos >= entry_end) ): - dbg("can't advance", indent=0) +## dbg("can't advance", indent=0) return False elif self._isTemplateChar(pos): self._AdjustField(pos) elif keycode in (wx.WXK_LEFT,wx.WXK_UP) and sel_start == sel_to and pos > 0 and self._isTemplateChar(pos-1): - dbg('adjusting field') +## dbg('adjusting field') self._AdjustField(pos) # treat as shifted up/down arrows as tab/reverse tab: @@ -2967,7 +3014,7 @@ class MaskedEditMixin: else: # treat arrows as normal, allowing selection # as appropriate: - dbg('using base ctrl event processing') +## dbg('using base ctrl event processing') event.Skip() else: if( (sel_to == self._fields[0]._extent[0] and keycode == wx.WXK_LEFT) @@ -2977,17 +3024,17 @@ class MaskedEditMixin: else: # treat arrows as normal, allowing selection # as appropriate: - dbg('using base event processing') +## dbg('using base event processing') event.Skip() keep_processing = False - dbg(indent=0) +## dbg(indent=0) return keep_processing def _OnCtrl_S(self, event): """ Default Ctrl-S handler; prints value information if demo enabled. """ - dbg("MaskedEditMixin::_OnCtrl_S") +## dbg("MaskedEditMixin::_OnCtrl_S") if self._demo: print 'MaskedEditMixin.GetValue() = "%s"\nMaskedEditMixin.GetPlainValue() = "%s"' % (self.GetValue(), self.GetPlainValue()) print "Valid? => " + str(self.IsValid()) @@ -2998,9 +3045,9 @@ class MaskedEditMixin: def _OnCtrl_X(self, event=None): """ Handles ctrl-x keypress in control and Cut operation on context menu. Should return False to skip other processing. """ - dbg("MaskedEditMixin::_OnCtrl_X", indent=1) +## dbg("MaskedEditMixin::_OnCtrl_X", indent=1) self.Cut() - dbg(indent=0) +## dbg(indent=0) return False def _OnCtrl_C(self, event=None): @@ -3012,17 +3059,17 @@ class MaskedEditMixin: def _OnCtrl_V(self, event=None): """ Handles ctrl-V keypress in control and Paste operation on context menu. Should return False to skip other processing. """ - dbg("MaskedEditMixin::_OnCtrl_V", indent=1) +## dbg("MaskedEditMixin::_OnCtrl_V", indent=1) self.Paste() - dbg(indent=0) +## dbg(indent=0) return False def _OnCtrl_Z(self, event=None): """ Handles ctrl-Z keypress in control and Undo operation on context menu. Should return False to skip other processing. """ - dbg("MaskedEditMixin::_OnCtrl_Z", indent=1) +## dbg("MaskedEditMixin::_OnCtrl_Z", indent=1) self.Undo() - dbg(indent=0) +## dbg(indent=0) return False def _OnCtrl_A(self,event=None): @@ -3039,7 +3086,7 @@ class MaskedEditMixin: def _OnErase(self, event=None): """ Handles backspace and delete keypress in control. Should return False to skip other processing.""" - dbg("MaskedEditMixin::_OnErase", indent=1) +## dbg("MaskedEditMixin::_OnErase", indent=1) sel_start, sel_to = self._GetSelection() ## check for a range of selected text if event is None: # called as action routine from Cut() operation. @@ -3062,7 +3109,7 @@ class MaskedEditMixin: and value[sel_to] == ' ' and key == wx.WXK_DELETE and not field._insertRight) ): if not wx.Validator_IsSilent(): wx.Bell() - dbg(indent=0) +## dbg(indent=0) return False @@ -3077,7 +3124,7 @@ class MaskedEditMixin: and (sel_to == end # and selection ends at right edge or sel_to < end and field._allowInsert)) ) ): # or allow right insert at any point in field - dbg('delete left') +## dbg('delete left') # if backspace but left of cursor is empty, adjust cursor right before deleting while( key == wx.WXK_BACK and sel_start == sel_to @@ -3086,7 +3133,7 @@ class MaskedEditMixin: sel_start += 1 sel_to = sel_start - dbg('sel_start, start:', sel_start, start) +## dbg('sel_start, start:', sel_start, start) if sel_start == sel_to: keep = sel_start -1 @@ -3100,7 +3147,7 @@ class MaskedEditMixin: signchar = value[0] newfield = signchar + newfield move_sign_into_field = True - dbg('cut newfield: "%s"' % newfield) +## dbg('cut newfield: "%s"' % newfield) # handle what should fill in from the left: left = "" @@ -3114,7 +3161,7 @@ class MaskedEditMixin: else: left += self._template[i] # this can produce strange results in combination with default values... newfield = left + newfield - dbg('filled newfield: "%s"' % newfield) +## dbg('filled newfield: "%s"' % newfield) newstr = value[:start] + newfield + value[end:] @@ -3164,7 +3211,7 @@ class MaskedEditMixin: 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]) +## 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: @@ -3182,24 +3229,24 @@ class MaskedEditMixin: if pos_adjust: newfield = newfield.rjust(end-start) newpos += pos_adjust - dbg("left='%s', right ='%s', newfield='%s'" %(left, right, newfield)) +## dbg("left='%s', right ='%s', newfield='%s'" %(left, right, newfield)) newstr = value[:start] + newfield + value[end:] pos = newpos else: if sel_start == sel_to: - dbg("current sel_start, sel_to:", sel_start, sel_to) +## dbg("current sel_start, sel_to:", sel_start, sel_to) if key == wx.WXK_BACK: sel_start, sel_to = sel_to-1, sel_to-1 - dbg("new sel_start, sel_to:", sel_start, sel_to) +## 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, ''): # preceding chars (if any) are zeros, blanks or fillchar; new char should be 0: newchar = '0' else: newchar = self._template[sel_to] ## get an original template character to "clear" the current char - dbg('value = "%s"' % value, 'value[%d] = "%s"' %(sel_start, value[sel_start])) +## 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] == '-': # erasing "template" sign char @@ -3236,36 +3283,38 @@ class MaskedEditMixin: # erased right-sign marker; get rid of left-sign marker: newstr = newstr[:left_signpos] + ' ' + newstr[left_signpos+1:] - dbg("oldstr:'%s'" % value, 'oldpos:', oldstart) - dbg("newstr:'%s'" % newstr, 'pos:', pos) +## dbg("oldstr:'%s'" % value, 'oldpos:', oldstart) +## dbg("newstr:'%s'" % newstr, 'pos:', pos) # if erasure results in an invalid field, disallow it: - dbg('field._validRequired?', field._validRequired) - dbg('field.IsValid("%s")?' % newstr[start:end], field.IsValid(newstr[start:end])) +## 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 wx.Validator_IsSilent(): wx.Bell() - dbg(indent=0) +## dbg(indent=0) return False # if erasure results in an invalid value, disallow it: if self._ctrl_constraints._validRequired and not self.IsValid(newstr): if not wx.Validator_IsSilent(): wx.Bell() - dbg(indent=0) +## dbg(indent=0) return False - dbg('setting value (later) to', newstr) +## dbg('setting value (later) to', newstr) wx.CallAfter(self._SetValue, newstr) - dbg('setting insertion point (later) to', pos) +## dbg('setting insertion point (later) to', pos) wx.CallAfter(self._SetInsertionPoint, pos) - dbg(indent=0) +## dbg(indent=0) + if newstr != value: + self.modified = True return False def _OnEnd(self,event): """ Handles End keypress in control. Should return False to skip other processing. """ - dbg("MaskedEditMixin::_OnEnd", indent=1) +## dbg("MaskedEditMixin::_OnEnd", indent=1) pos = self._adjustPos(self._GetInsertionPoint(), event.GetKeyCode()) if not event.ControlDown(): end = self._masklength # go to end of control @@ -3282,48 +3331,51 @@ class MaskedEditMixin: # - cursor not in same field # - or at or past last input already # - or current selection = end of current field: -## dbg('field != field_end?', field != field_end) -## dbg('sel_to >= end_of_input?', sel_to >= end_of_input) +#### dbg('field != field_end?', field != field_end) +#### dbg('sel_to >= end_of_input?', sel_to >= end_of_input) if field != field_end or sel_to >= end_of_input: edit_start, edit_end = field._extent -## dbg('edit_end:', edit_end) -## dbg('sel_to:', sel_to) -## dbg('sel_to == edit_end?', sel_to == edit_end) -## dbg('field._index < self._field_indices[-1]?', field._index < self._field_indices[-1]) +#### dbg('edit_end:', edit_end) +#### dbg('sel_to:', sel_to) +#### dbg('sel_to == edit_end?', sel_to == edit_end) +#### dbg('field._index < self._field_indices[-1]?', field._index < self._field_indices[-1]) if sel_to == edit_end and field._index < self._field_indices[-1]: edit_start, edit_end = self._FindFieldExtent(self._findNextEntry(edit_end)) # go to end of next field: end = edit_end - dbg('end moved to', end) +## dbg('end moved to', end) elif sel_to == edit_end and field._index == self._field_indices[-1]: # already at edit end of last field; select to end of control: end = self._masklength - dbg('end moved to', end) +## dbg('end moved to', end) else: end = edit_end # select to end of current field - dbg('end moved to ', end) +## dbg('end moved to ', end) else: # select to current end of input end = end_of_input -## dbg('pos:', pos, 'end:', end) +#### dbg('pos:', pos, 'end:', end) if event.ShiftDown(): if not event.ControlDown(): - dbg("shift-end; select to end of control") +## dbg("shift-end; select to end of control") + pass else: - dbg("shift-ctrl-end; select to end of non-whitespace") +## dbg("shift-ctrl-end; select to end of non-whitespace") + pass wx.CallAfter(self._SetInsertionPoint, pos) wx.CallAfter(self._SetSelection, pos, end) else: if not event.ControlDown(): - dbg('go to end of control:') +## dbg('go to end of control:') + pass wx.CallAfter(self._SetInsertionPoint, end) wx.CallAfter(self._SetSelection, end, end) - dbg(indent=0) +## dbg(indent=0) return False @@ -3332,14 +3384,14 @@ class MaskedEditMixin: Changes the event to look like a tab event, so we can then call event.Skip() on it, and have the parent form "do the right thing." """ - dbg('MaskedEditMixin::OnReturn') +## dbg('MaskedEditMixin::OnReturn') event.m_keyCode = wx.WXK_TAB event.Skip() def _OnHome(self,event): """ Handles Home keypress in control. Should return False to skip other processing.""" - dbg("MaskedEditMixin::_OnHome", indent=1) +## dbg("MaskedEditMixin::_OnHome", indent=1) pos = self._adjustPos(self._GetInsertionPoint(), event.GetKeyCode()) sel_start, sel_to = self._GetSelection() @@ -3348,13 +3400,13 @@ class MaskedEditMixin: # 1) shift: select from start of control to end of current # selection. if event.ShiftDown() and not event.ControlDown(): - dbg("shift-home; select to start of control") +## dbg("shift-home; select to start of control") start = 0 end = sel_start # 2) no shift, no control: move cursor to beginning of control. elif not event.ControlDown(): - dbg("home; move to start of control") +## dbg("home; move to start of control") start = 0 end = 0 @@ -3380,10 +3432,10 @@ class MaskedEditMixin: start = 0 if not event.ShiftDown(): - dbg("ctrl-home; move to beginning of field") +## dbg("ctrl-home; move to beginning of field") end = start else: - dbg("shift-ctrl-home; select to beginning of field") +## dbg("shift-ctrl-home; select to beginning of field") end = sel_to else: @@ -3402,12 +3454,12 @@ class MaskedEditMixin: else: end = start end_of_field = False - dbg("shift-ctrl-home; unselect to beginning of field") +## dbg("shift-ctrl-home; unselect to beginning of field") - dbg('queuing new sel_start, sel_to:', (start, end)) +## dbg('queuing new sel_start, sel_to:', (start, end)) wx.CallAfter(self._SetInsertionPoint, start) wx.CallAfter(self._SetSelection, start, end) - dbg(indent=0) +## dbg(indent=0) return False @@ -3419,19 +3471,19 @@ class MaskedEditMixin: control-shift-TAB, these events are not sent to the controls by the framework. """ - dbg('MaskedEditMixin::_OnChangeField', indent = 1) +## dbg('MaskedEditMixin::_OnChangeField', indent = 1) # determine end of current field: pos = self._GetInsertionPoint() - dbg('current pos:', pos) +## dbg('current pos:', pos) sel_start, sel_to = self._GetSelection() if self._masklength < 0: # no fields; process tab normally self._AdjustField(pos) if event.GetKeyCode() == wx.WXK_TAB: - dbg('tab to next ctrl') +## dbg('tab to next ctrl') event.Skip() #else: do nothing - dbg(indent=0) +## dbg(indent=0) return False @@ -3446,41 +3498,41 @@ class MaskedEditMixin: index = field._index field_start = field._extent[0] if pos < field_start: - dbg('cursor before 1st field; cannot change to a previous field') +## dbg('cursor before 1st field; cannot change to a previous field') if not wx.Validator_IsSilent(): wx.Bell() return False if event.ControlDown(): - dbg('queuing select to beginning of field:', field_start, pos) +## dbg('queuing select to beginning of field:', field_start, pos) wx.CallAfter(self._SetInsertionPoint, field_start) wx.CallAfter(self._SetSelection, field_start, pos) - dbg(indent=0) +## dbg(indent=0) return False elif index == 0: # We're already in the 1st field; process shift-tab normally: self._AdjustField(pos) if event.GetKeyCode() == wx.WXK_TAB: - dbg('tab to previous ctrl') +## dbg('tab to previous ctrl') event.Skip() else: - dbg('position at beginning') +## dbg('position at beginning') wx.CallAfter(self._SetInsertionPoint, field_start) - dbg(indent=0) +## dbg(indent=0) return False else: # find beginning of previous field: begin_prev = self._FindField(field_start-1)._extent[0] self._AdjustField(pos) - dbg('repositioning to', begin_prev) +## dbg('repositioning to', begin_prev) wx.CallAfter(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)) +## dbg('queuing selection to (%d, %d)' % (edit_start, edit_end)) wx.CallAfter(self._SetInsertionPoint, edit_start) wx.CallAfter(self._SetSelection, edit_start, edit_end) - dbg(indent=0) +## dbg(indent=0) return False else: @@ -3488,14 +3540,14 @@ class MaskedEditMixin: field = self._FindField(sel_to) field_start, field_end = field._extent if event.ControlDown(): - dbg('queuing select to end of field:', pos, field_end) +## dbg('queuing select to end of field:', pos, field_end) wx.CallAfter(self._SetInsertionPoint, pos) wx.CallAfter(self._SetSelection, pos, field_end) - dbg(indent=0) +## dbg(indent=0) return False else: if pos < field_start: - dbg('cursor before 1st field; go to start of field') +## dbg('cursor before 1st field; go to start of field') wx.CallAfter(self._SetInsertionPoint, field_start) if field._selectOnFieldEntry: wx.CallAfter(self._SetSelection, field_start, field_end) @@ -3503,29 +3555,29 @@ class MaskedEditMixin: wx.CallAfter(self._SetSelection, field_start, field_start) return False # else... - dbg('end of current field:', field_end) - dbg('go to next field') +## 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() == wx.WXK_TAB: - dbg('tab to next ctrl') +## dbg('tab to next ctrl') event.Skip() else: - dbg('position at end') +## dbg('position at end') wx.CallAfter(self._SetInsertionPoint, field_end) - dbg(indent=0) +## dbg(indent=0) return False else: # we have to find the start of the next field next_pos = self._findNextEntry(field_end) if next_pos == field_end: - dbg('already in last field') +## dbg('already in last field') self._AdjustField(pos) if event.GetKeyCode() == wx.WXK_TAB: - dbg('tab to next ctrl') +## dbg('tab to next ctrl') event.Skip() #else: do nothing - dbg(indent=0) +## dbg(indent=0) return False else: self._AdjustField( pos ) @@ -3534,31 +3586,31 @@ class MaskedEditMixin: field = self._FindField(next_pos) edit_start, edit_end = field._extent if field._selectOnFieldEntry: - dbg('move to ', next_pos) +## dbg('move to ', next_pos) wx.CallAfter(self._SetInsertionPoint, next_pos) edit_start, edit_end = self._FindFieldExtent(next_pos) - dbg('queuing select', edit_start, edit_end) +## dbg('queuing select', edit_start, edit_end) wx.CallAfter(self._SetSelection, edit_start, edit_end) else: if field._insertRight: next_pos = field._extent[1] - dbg('move to ', next_pos) +## dbg('move to ', next_pos) wx.CallAfter(self._SetInsertionPoint, next_pos) - dbg(indent=0) +## dbg(indent=0) return False def _OnDecimalPoint(self, event): - dbg('MaskedEditMixin::_OnDecimalPoint', indent=1) +## dbg('MaskedEditMixin::_OnDecimalPoint', indent=1) pos = self._adjustPos(self._GetInsertionPoint(), event.GetKeyCode()) if self._isFloat: ## handle float value, move to decimal place - dbg('key == Decimal tab; decimal pos:', self._decimalpos) +## 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) +## dbg('value: "%s"' % self._GetValue(), "clipped_text:'%s'" % clipped_text) newstr = self._adjustFloat(clipped_text) else: newstr = self._adjustFloat(value) @@ -3567,46 +3619,46 @@ class MaskedEditMixin: start, end = fraction._extent wx.CallAfter(self._SetInsertionPoint, start) if fraction._selectOnFieldEntry: - dbg('queuing selection after decimal point to:', (start, end)) +## dbg('queuing selection after decimal point to:', (start, end)) wx.CallAfter(self._SetSelection, start, end) keep_processing = False if self._isInt: ## handle integer value, truncate from current position - dbg('key == Integer decimal event') +## dbg('key == Integer decimal event') value = self._GetValue() clipped_text = value[0:pos] - dbg('value: "%s"' % self._GetValue(), "clipped_text:'%s'" % clipped_text) +## dbg('value: "%s"' % self._GetValue(), "clipped_text:'%s'" % clipped_text) newstr = self._adjustInt(clipped_text) - dbg('newstr: "%s"' % newstr) +## dbg('newstr: "%s"' % newstr) wx.CallAfter(self._SetValue, newstr) newpos = len(newstr.rstrip()) if newstr.find(')') != -1: newpos -= 1 # (don't move past right paren) wx.CallAfter(self._SetInsertionPoint, newpos) keep_processing = False - dbg(indent=0) +## dbg(indent=0) def _OnChangeSign(self, event): - dbg('MaskedEditMixin::_OnChangeSign', indent=1) +## dbg('MaskedEditMixin::_OnChangeSign', indent=1) key = event.GetKeyCode() pos = self._adjustPos(self._GetInsertionPoint(), key) value = self._eraseSelection() integer = self._fields[0] start, end = integer._extent -## dbg('adjusted pos:', pos) +#### dbg('adjusted pos:', pos) if chr(key) in ('-','+','(', ')') or (chr(key) == " " and pos == self._signpos): cursign = self._isNeg - dbg('cursign:', cursign) +## dbg('cursign:', cursign) if chr(key) in ('-','(', ')'): self._isNeg = (not self._isNeg) ## flip value else: self._isNeg = False - dbg('isNeg?', self._isNeg) +## 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) +## dbg('text:"%s"' % text, 'signpos:', self._signpos, 'right_signpos:', self._right_signpos) if text is None: text = value @@ -3616,17 +3668,17 @@ class MaskedEditMixin: else: text = text[:self._signpos] + '-' + text[self._signpos+1:] else: -## dbg('self._isNeg?', self._isNeg, 'self.IsValid(%s)' % text, self.IsValid(text)) +#### dbg('self._isNeg?', self._isNeg, 'self.IsValid(%s)' % text, self.IsValid(text)) if 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') +## dbg('clearing self._isNeg') self._isNeg = False wx.CallAfter(self._SetValue, text) wx.CallAfter(self._applyFormatting) - dbg('pos:', pos, 'signpos:', self._signpos) +## dbg('pos:', pos, 'signpos:', self._signpos) if pos == self._signpos or integer.IsEmpty(text[start:end]): wx.CallAfter(self._SetInsertionPoint, self._signpos+1) else: @@ -3635,7 +3687,7 @@ class MaskedEditMixin: keep_processing = False else: keep_processing = True - dbg(indent=0) +## dbg(indent=0) return keep_processing @@ -3644,7 +3696,7 @@ class MaskedEditMixin: This handler is only registered if the mask is a numeric mask. It allows the insertion of ',' or '.' if appropriate. """ - dbg('MaskedEditMixin::_OnGroupChar', indent=1) +## dbg('MaskedEditMixin::_OnGroupChar', indent=1) keep_processing = True pos = self._adjustPos(self._GetInsertionPoint(), event.GetKeyCode()) sel_start, sel_to = self._GetSelection() @@ -3656,7 +3708,7 @@ class MaskedEditMixin: if keep_processing: newstr, newpos = self._insertKey(groupchar, pos, sel_start, sel_to, self._GetValue() ) - dbg("str with '%s' inserted:" % groupchar, '"%s"' % newstr) +## dbg("str with '%s' inserted:" % groupchar, '"%s"' % newstr) if self._ctrl_constraints._validRequired and not self.IsValid(newstr): keep_processing = False if not wx.Validator_IsSilent(): @@ -3666,7 +3718,7 @@ class MaskedEditMixin: wx.CallAfter(self._SetValue, newstr) wx.CallAfter(self._SetInsertionPoint, newpos) keep_processing = False - dbg(indent=0) +## dbg(indent=0) return keep_processing @@ -3700,7 +3752,7 @@ class MaskedEditMixin: def _OnAutoCompleteField(self, event): - dbg('MaskedEditMixin::_OnAutoCompleteField', indent =1) +## dbg('MaskedEditMixin::_OnAutoCompleteField', indent =1) pos = self._GetInsertionPoint() field = self._FindField(pos) edit_start, edit_end, slice = self._FindFieldExtent(pos, getslice=True) @@ -3714,10 +3766,10 @@ class MaskedEditMixin: text = slice text = text.strip() keep_processing = True # (assume True to start) - dbg('field._hasList?', field._hasList) +## dbg('field._hasList?', field._hasList) if field._hasList: - dbg('choices:', field._choices) - dbg('compareChoices:', field._compareChoices) +## dbg('choices:', field._choices) +## dbg('compareChoices:', field._compareChoices) choices, choice_required = field._compareChoices, field._choiceRequired if keycode in (wx.WXK_PRIOR, wx.WXK_UP): direction = -1 @@ -3736,10 +3788,10 @@ class MaskedEditMixin: or (keycode == wx.WXK_DOWN and partial_match) ) ): # We're allowed to auto-complete: - dbg('match found') +## dbg('match found') value = self._GetValue() newvalue = value[:edit_start] + field._choices[match_index] + value[edit_end:] - dbg('setting value to "%s"' % newvalue) +## dbg('setting value to "%s"' % newvalue) self._SetValue(newvalue) self._SetInsertionPoint(min(edit_end, len(newvalue.rstrip()))) self._OnAutoSelect(field, match_index) @@ -3757,7 +3809,7 @@ class MaskedEditMixin: keep_processing = self._OnArrow(event) # else some other key; keep processing the key - dbg('keep processing?', keep_processing, indent=0) +## dbg('keep processing?', keep_processing, indent=0) return keep_processing @@ -3766,7 +3818,7 @@ class MaskedEditMixin: Function called if autoselect feature is enabled and entire control is selected: """ - dbg('MaskedEditMixin::OnAutoSelect', field._index) +## dbg('MaskedEditMixin::OnAutoSelect', field._index) if match_index is not None: field._autoCompleteIndex = match_index @@ -3783,9 +3835,9 @@ class MaskedEditMixin: The function returns a 2-tuple, with the 2nd element being a boolean that indicates if partial match was necessary. """ - dbg('autoComplete(direction=', direction, 'choices=',choices, 'value=',value,'compareNoCase?', compareNoCase, 'current_index:', current_index, indent=1) +## 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) +## dbg('nothing to match against', indent=0) return (None, False) partial_match = False @@ -3795,28 +3847,28 @@ class MaskedEditMixin: last_index = len(choices) - 1 if value in choices: - dbg('"%s" 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)) +## dbg('matched "%s" (%d)' % (choices[index], index)) if direction == -1: - dbg('going to previous') +## dbg('going to previous') if index == 0: index = len(choices) - 1 else: index -= 1 else: if index == len(choices) - 1: index = 0 else: index += 1 - dbg('change value to "%s" (%d)' % (choices[index], index)) +## 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:') +## dbg('no match; try to auto-complete:') match = None - dbg('searching for "%s"' % value) +## dbg('searching for "%s"' % value) if current_index is None: indices = range(len(choices)) if direction == -1: @@ -3824,23 +3876,25 @@ class MaskedEditMixin: else: if 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) +## 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) -## dbg('indices:', indices) +## 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) +#### dbg('indices:', indices) for index in indices: choice = choices[index] if choice.find(value, 0) == 0: - dbg('match found:', choice) +## dbg('match found:', choice) match = index break else: dbg('choice: "%s" - no match' % choice) if match is not None: - dbg('matched', match) +## dbg('matched', match) + pass else: - dbg('no match found') - dbg(indent=0) +## dbg('no match found') + pass +## dbg(indent=0) return (match, partial_match) @@ -3897,31 +3951,31 @@ class MaskedEditMixin: Checks the current insertion point position and adjusts it if necessary to skip over non-editable characters. """ - dbg('_adjustPos', pos, key, indent=1) +## dbg('_adjustPos', pos, key, indent=1) sel_start, sel_to = self._GetSelection() # If a numeric or decimal mask, and negatives allowed, reserve the # first space for sign, and last one if using parens. if( self._signOk and ((pos == self._signpos and key in (ord('-'), ord('+'), ord(' ')) ) or self._useParens and pos == self._masklength -1)): - dbg('adjusted pos:', pos, indent=0) +## dbg('adjusted pos:', pos, indent=0) return pos if key not in self._nav: field = self._FindField(pos) - dbg('field._insertRight?', field._insertRight) +## dbg('field._insertRight?', field._insertRight) if field._insertRight: # if allow right-insert start, end = field._extent slice = self._GetValue()[start:end].strip() field_len = end - start if pos == end: # if cursor at right edge of field # if not filled or supposed to stay in field, keep current position -## dbg('pos==end') -## dbg('len (slice):', len(slice)) -## dbg('field_len?', field_len) -## dbg('pos==end; len (slice) < field_len?', len(slice) < field_len) -## dbg('not field._moveOnFieldFull?', not field._moveOnFieldFull) +#### dbg('pos==end') +#### dbg('len (slice):', len(slice)) +#### dbg('field_len?', field_len) +#### dbg('pos==end; len (slice) < field_len?', len(slice) < field_len) +#### dbg('not field._moveOnFieldFull?', not field._moveOnFieldFull) if len(slice) == field_len and field._moveOnFieldFull: # move cursor to next field: pos = self._findNextEntry(pos) @@ -3967,7 +4021,7 @@ class MaskedEditMixin: self._SetInsertionPoint(pos) if pos < sel_to: # restore selection self._SetSelection(pos, sel_to) - dbg('adjusted pos:', pos, indent=0) +## dbg('adjusted pos:', pos, indent=0) return pos @@ -3975,24 +4029,24 @@ class MaskedEditMixin: """ 'Fixes' an floating point control. Collapses spaces, right-justifies, etc. """ - dbg('MaskedEditMixin::_adjustFloat, candidate = "%s"' % candidate, indent=1) +## dbg('MaskedEditMixin::_adjustFloat, candidate = "%s"' % candidate, indent=1) lenInt,lenFraction = [len(s) for s in self._mask.split('.')] ## Get integer, fraction lengths if candidate is None: value = self._GetValue() else: value = candidate - dbg('value = "%(value)s"' % locals(), 'len(value):', len(value)) +## 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) +## dbg('adjusted intStr: "%s"' % intStr) lenInt = len(intStr) fracStr = fracStr + ('0'*(lenFraction-len(fracStr))) # add trailing spaces to decimal - dbg('intStr "%(intStr)s"' % locals()) - dbg('lenInt:', lenInt) +## dbg('intStr "%(intStr)s"' % locals()) +## dbg('lenInt:', lenInt) intStr = string.rjust( intStr[-lenInt:], lenInt) - dbg('right-justifed intStr = "%(intStr)s"' % locals()) +## dbg('right-justifed intStr = "%(intStr)s"' % locals()) newvalue = intStr + self._decimalChar + fracStr if self._signOk: @@ -4014,23 +4068,23 @@ class MaskedEditMixin: else: newvalue = newvalue[:-1] + ' ' - dbg('newvalue = "%s"' % newvalue) +## dbg('newvalue = "%s"' % newvalue) if candidate is None: wx.CallAfter(self._SetValue, newvalue) - dbg(indent=0) +## dbg(indent=0) return newvalue def _adjustInt(self, candidate=None): """ 'Fixes' an integer control. Collapses spaces, right or left-justifies.""" - dbg("MaskedEditMixin::_adjustInt", candidate) +## dbg("MaskedEditMixin::_adjustInt", candidate) lenInt = self._masklength if candidate is None: value = self._GetValue() else: value = candidate intStr = self._fields[0]._AdjustField(value) intStr = intStr.strip() # drop extra spaces - dbg('adjusted field: "%s"' % intStr) +## dbg('adjusted field: "%s"' % intStr) if self._isNeg and intStr.find('-') == -1 and intStr.find('(') == -1: if self._useParens: @@ -4065,20 +4119,20 @@ class MaskedEditMixin: 'Fixes' a date control, expanding the year if it can. Applies various self-formatting options. """ - dbg("MaskedEditMixin::_adjustDate", indent=1) +## dbg("MaskedEditMixin::_adjustDate", indent=1) if candidate is None: text = self._GetValue() else: text = candidate - dbg('text=', text) +## dbg('text=', text) if self._datestyle == "YMD": year_field = 0 else: year_field = 2 - dbg('getYear: "%s"' % getYear(text, self._datestyle)) +## dbg('getYear: "%s"' % getYear(text, self._datestyle)) year = string.replace( getYear( text, self._datestyle),self._fields[year_field]._fillChar,"") # drop extra fillChars month = getMonth( text, self._datestyle) day = getDay( text, self._datestyle) - dbg('self._datestyle:', self._datestyle, 'year:', year, 'Month', month, 'day:', day) +## dbg('self._datestyle:', self._datestyle, 'year:', year, 'Month', month, 'day:', day) yearVal = None yearstart = self._dateExtent - 4 @@ -4092,7 +4146,7 @@ class MaskedEditMixin: try: yearVal = int(year) except: - dbg('bad year=', year) +## dbg('bad year=', year) year = text[yearstart:self._dateExtent] if len(year) < 4 and yearVal: @@ -4119,22 +4173,22 @@ class MaskedEditMixin: 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) +## dbg('newdate: "%s"' % text, indent=0) return text def _goEnd(self, getPosOnly=False): """ Moves the insertion point to the end of user-entry """ - dbg("MaskedEditMixin::_goEnd; getPosOnly:", getPosOnly, indent=1) +## dbg("MaskedEditMixin::_goEnd; getPosOnly:", getPosOnly, indent=1) text = self._GetValue() -## dbg('text: "%s"' % text) +#### dbg('text: "%s"' % text) i = 0 if len(text.rstrip()): for i in range( min( self._masklength-1, len(text.rstrip())), -1, -1): -## dbg('i:', i, 'self._isMaskChar(%d)' % i, self._isMaskChar(i)) +#### dbg('i:', i, 'self._isMaskChar(%d)' % i, self._isMaskChar(i)) if self._isMaskChar(i): char = text[i] -## dbg("text[%d]: '%s'" % (i, char)) +#### dbg("text[%d]: '%s'" % (i, char)) if char != ' ': i += 1 break @@ -4148,8 +4202,8 @@ class MaskedEditMixin: start, end = field._extent if field._insertRight and pos < end: pos = end - dbg('next pos:', pos) - dbg(indent=0) +## dbg('next pos:', pos) +## dbg(indent=0) if getPosOnly: return pos else: @@ -4158,13 +4212,13 @@ class MaskedEditMixin: def _goHome(self, getPosOnly=False): """ Moves the insertion point to the beginning of user-entry """ - dbg("MaskedEditMixin::_goHome; getPosOnly:", getPosOnly, indent=1) +## dbg("MaskedEditMixin::_goHome; getPosOnly:", getPosOnly, indent=1) text = self._GetValue() for i in range(self._masklength): if self._isMaskChar(i): break pos = max(i, 0) - dbg(indent=0) +## dbg(indent=0) if getPosOnly: return pos else: @@ -4183,7 +4237,7 @@ class MaskedEditMixin: okchars += " " if okchars and field._includeChars: ## any additional included characters? okchars += field._includeChars -## dbg('okchars[%d]:' % pos, okchars) +#### dbg('okchars[%d]:' % pos, okchars) return okchars @@ -4207,7 +4261,7 @@ class MaskedEditMixin: def _isCharAllowed(self, char, pos, checkRegex=False, allowAutoSelect=True, ignoreInsertRight=False): """ Returns True if character is allowed at the specific position, otherwise False.""" - dbg('_isCharAllowed', char, pos, checkRegex, indent=1) +## dbg('_isCharAllowed', char, pos, checkRegex, indent=1) field = self._FindField(pos) right_insert = False @@ -4238,21 +4292,21 @@ class MaskedEditMixin: if( (sel_start, sel_to) == field._extent or (pos == end and input_len < field_len)): pos = end - 1 - dbg('pos = end - 1 = ', pos, 'right_insert? 1') +## dbg('pos = end - 1 = ', pos, 'right_insert? 1') right_insert = True elif( field._allowInsert and sel_start == sel_to and (sel_to == end or (sel_to < self._masklength and value[sel_start] != field._fillChar)) and input_len < field_len ): pos = sel_to - 1 # where character will go - dbg('pos = sel_to - 1 = ', pos, 'right_insert? 1') +## dbg('pos = sel_to - 1 = ', pos, 'right_insert? 1') right_insert = True # else leave pos alone... else: - dbg('pos stays ', pos, 'right_insert? 0') - +## dbg('pos stays ', pos, 'right_insert? 0') + pass if self._isTemplateChar( pos ): ## if a template character, return empty - dbg('%d is a template character; returning False' % pos, indent=0) +## dbg('%d is a template character; returning False' % pos, indent=0) return False if self._isMaskChar( pos ): @@ -4269,11 +4323,11 @@ class MaskedEditMixin: elif self._useParens and (self._isInt or (self._isFloat and pos > self._decimalpos)): okChars += ')' -## dbg('%s in %s?' % (char, okChars), char in okChars) +#### dbg('%s in %s?' % (char, okChars), char in okChars) approved = char in okChars if approved and checkRegex: - dbg("checking appropriate regex's") +## dbg("checking appropriate regex's") value = self._eraseSelection(self._GetValue()) if right_insert: at = pos+1 @@ -4283,21 +4337,21 @@ class MaskedEditMixin: 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) +## dbg('newvalue: "%s"' % newvalue) fields = [self._FindField(pos)] + [self._ctrl_constraints] for field in fields: # includes fields[-1] == "ctrl_constraints" if field._regexMask and field._filter: - dbg('checking vs. regex') +## dbg('checking vs. regex') start, end = field._extent slice = newvalue[start:end] approved = (re.match( field._filter, slice) is not None) - dbg('approved?', approved) +## dbg('approved?', approved) if not approved: break - dbg(indent=0) +## dbg(indent=0) return approved else: - dbg('%d is a !???! character; returning False', indent=0) +## dbg('%d is a !???! character; returning False', indent=0) return False @@ -4306,22 +4360,22 @@ class MaskedEditMixin: Need to find a way to call this whenever the value changes, in case the control's value has been changed or set programatically. """ - dbg(suspend=1) - dbg('MaskedEditMixin::_applyFormatting', indent=1) +## dbg(suspend=1) +## dbg('MaskedEditMixin::_applyFormatting', indent=1) # Handle negative numbers if self._signOk: text, signpos, right_signpos = self._getSignedValue() - dbg('text: "%s", signpos:' % text, signpos) +## 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) +## 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') +## dbg('setting _isNeg to True') self._isNeg = True - dbg('self._isNeg:', self._isNeg) +## dbg('self._isNeg:', self._isNeg) if self._signOk and self._isNeg: fc = self._signedForegroundColour @@ -4332,32 +4386,32 @@ class MaskedEditMixin: c =fc._name else: c = fc - dbg('setting foreground to', c) +## dbg('setting foreground to', c) self.SetForegroundColour(fc) if self._valid: - dbg('valid') +## dbg('valid') if self.IsEmpty(): bc = self._emptyBackgroundColour else: bc = self._validBackgroundColour else: - dbg('invalid') +## dbg('invalid') bc = self._invalidBackgroundColour if hasattr(bc, '_name'): c =bc._name else: c = bc - dbg('setting background to', c) +## dbg('setting background to', c) self.SetBackgroundColour(bc) self._Refresh() - dbg(indent=0, suspend=0) +## dbg(indent=0, suspend=0) def _getAbsValue(self, candidate=None): """ Return an unsigned value (i.e. strip the '-' prefix if any), and sign position(s). """ - dbg('MaskedEditMixin::_getAbsValue; candidate="%s"' % candidate, indent=1) +## dbg('MaskedEditMixin::_getAbsValue; candidate="%s"' % candidate, indent=1) if candidate is None: text = self._GetValue() else: text = candidate right_signpos = text.find(')') @@ -4366,14 +4420,15 @@ class MaskedEditMixin: if self._ctrl_constraints._alignRight and self._fields[0]._fillChar == ' ': signpos = text.find('-') if signpos == -1: - dbg('no - found; searching for (') +## dbg('no - found; searching for (') signpos = text.find('(') elif signpos != -1: - dbg('- found at', signpos) +## dbg('- found at', signpos) + pass 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) +## 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: @@ -4381,13 +4436,13 @@ class MaskedEditMixin: 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)) +## 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 # empty value; use penultimate space - dbg('signpos:', signpos) +## dbg('signpos:', signpos) if signpos >= 0: text = text[:signpos] + ' ' + text[signpos+1:] @@ -4410,25 +4465,25 @@ class MaskedEditMixin: # figure out where it ought to go: right_signpos = self._masklength - 1 # initial guess if not self._ctrl_constraints._alignRight: - dbg('not right-aligned') +## 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) +## dbg('right_signpos:', right_signpos) groupchar = self._fields[0]._groupChar try: value = long(text.replace(groupchar,'').replace('(','-').replace(')','').replace(' ', '')) except: - dbg('invalid number', indent=0) +## dbg('invalid number', indent=0) return None, signpos, right_signpos else: # float value try: groupchar = self._fields[0]._groupChar value = float(text.replace(groupchar,'').replace(self._decimalChar, '.').replace('(', '-').replace(')','').replace(' ', '')) - dbg('value:', value) +## dbg('value:', value) except: value = None @@ -4440,16 +4495,16 @@ class MaskedEditMixin: text = text[:signpos] + self._template[signpos] + text[signpos+1:] else: # look forwards up to the decimal point for the 1st non-digit - dbg('decimal pos:', self._decimalpos) - dbg('text: "%s"' % text) +## dbg('decimal pos:', self._decimalpos) +## dbg('text: "%s"' % text) if self._signOk: signpos = self._decimalpos - (len(text[:self._decimalpos].lstrip()) + 1) - # prevent checking for empty string - Tomo - Wed 14 Jan 2004 03:19:09 PM CET + # prevent checking for empty string - Tomo - Wed 14 Jan 2004 03:19:09 PM CET if len(text) >= signpos+1 and text[signpos+1] in ('-','('): signpos += 1 else: signpos = -1 - dbg('signpos:', signpos) +## dbg('signpos:', signpos) if self._useParens: if self._signOk: @@ -4464,11 +4519,11 @@ class MaskedEditMixin: right_signpos = -1 if value is None: - dbg('invalid number') +## dbg('invalid number') text = None - dbg('abstext = "%s"' % text, 'signpos:', signpos, 'right_signpos:', right_signpos) - dbg(indent=0) +## dbg('abstext = "%s"' % text, 'signpos:', signpos, 'right_signpos:', right_signpos) +## dbg(indent=0) return text, signpos, right_signpos @@ -4476,7 +4531,7 @@ class MaskedEditMixin: """ Return a signed value by adding a "-" prefix if the value is set to negative, or a space if positive. """ - dbg('MaskedEditMixin::_getSignedValue; candidate="%s"' % candidate, indent=1) +## dbg('MaskedEditMixin::_getSignedValue; candidate="%s"' % candidate, indent=1) if candidate is None: text = self._GetValue() else: text = candidate @@ -4484,7 +4539,7 @@ class MaskedEditMixin: abstext, signpos, right_signpos = self._getAbsValue(text) if self._signOk: if abstext is None: - dbg(indent=0) +## dbg(indent=0) return abstext, signpos, right_signpos if self._isNeg or text[signpos] in ('-', '('): @@ -4504,8 +4559,8 @@ class MaskedEditMixin: text = text[:right_signpos] + ')' + text[right_signpos+1:] else: text = abstext - dbg('signedtext = "%s"' % text, 'signpos:', signpos, 'right_signpos', right_signpos) - dbg(indent=0) +## dbg('signedtext = "%s"' % text, 'signpos:', signpos, 'right_signpos', right_signpos) +## dbg(indent=0) return text, signpos, right_signpos @@ -4513,13 +4568,13 @@ class MaskedEditMixin: """ Returns control's value stripped of the template text. plainvalue = MaskedEditMixin.GetPlainValue() """ - dbg('MaskedEditMixin::GetPlainValue; candidate="%s"' % candidate, indent=1) +## dbg('MaskedEditMixin::GetPlainValue; candidate="%s"' % candidate, indent=1) if candidate is None: text = self._GetValue() else: text = candidate if self.IsEmpty(): - dbg('returned ""', indent=0) +## dbg('returned ""', indent=0) return "" else: plain = "" @@ -4528,9 +4583,9 @@ class MaskedEditMixin: plain += text[idx] if self._isFloat or self._isInt: - dbg('plain so far: "%s"' % plain) +## dbg('plain so far: "%s"' % plain) plain = plain.replace('(', '-').replace(')', ' ') - dbg('plain after sign regularization: "%s"' % plain) +## dbg('plain after sign regularization: "%s"' % plain) if self._signOk and self._isNeg and plain.count('-') == 0: # must be in reserved position; add to "plain value" @@ -4541,9 +4596,9 @@ class MaskedEditMixin: plain = ' ' * lpad + plain.replace(',','') else: plain = plain.replace(',','') - dbg('plain after pad and group:"%s"' % plain) +## dbg('plain after pad and group:"%s"' % plain) - dbg('returned "%s"' % plain.rstrip(), indent=0) +## dbg('returned "%s"' % plain.rstrip(), indent=0) return plain.rstrip() @@ -4554,19 +4609,19 @@ class MaskedEditMixin: """ if value is None: value = self._GetValue() if value == self._template and not self._defaultValue: -## dbg("IsEmpty? 1 (value == self._template and not self._defaultValue)") +#### dbg("IsEmpty? 1 (value == self._template and not self._defaultValue)") return True # (all mask chars == fillChar by defn) elif value == self._template: empty = True for pos in range(len(self._template)): -## dbg('isMaskChar(%(pos)d)?' % locals(), self._isMaskChar(pos)) -## dbg('value[%(pos)d] != self._fillChar?' %locals(), value[pos] != self._fillChar[pos]) +#### dbg('isMaskChar(%(pos)d)?' % locals(), self._isMaskChar(pos)) +#### dbg('value[%(pos)d] != self._fillChar?' %locals(), value[pos] != self._fillChar[pos]) if self._isMaskChar(pos) and value[pos] not in (' ', self._fillChar[pos]): empty = False -## dbg("IsEmpty? %(empty)d (do all mask chars == fillChar?)" % locals()) +#### dbg("IsEmpty? %(empty)d (do all mask chars == fillChar?)" % locals()) return empty else: -## dbg("IsEmpty? 0 (value doesn't match template)") +#### dbg("IsEmpty? 0 (value doesn't match template)") return False @@ -4582,26 +4637,26 @@ class MaskedEditMixin: def IsValid(self, value=None): """ Indicates whether the value specified (or the current value of the control if not specified) is considered valid.""" -## dbg('MaskedEditMixin::IsValid("%s")' % value, indent=1) +#### dbg('MaskedEditMixin::IsValid("%s")' % value, indent=1) if value is None: value = self._GetValue() ret = self._CheckValid(value) -## dbg(indent=0) +#### dbg(indent=0) return ret def _eraseSelection(self, value=None, sel_start=None, sel_to=None): """ Used to blank the selection when inserting a new character. """ - dbg("MaskedEditMixin::_eraseSelection", indent=1) +## dbg("MaskedEditMixin::_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() ## check for a range of selected text - dbg('value: "%s"' % value) - dbg("current sel_start, sel_to:", sel_start, sel_to) +## 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) +## dbg('found sign (%s) at' % newvalue[i], i) # balance parentheses: if newvalue[i] == '(': @@ -4624,14 +4679,14 @@ class MaskedEditMixin: newvalue[i] = self._template[i] value = string.join(newvalue,"") - dbg('new value: "%s"' % value) - dbg(indent=0) +## dbg('new value: "%s"' % value) +## dbg(indent=0) return value def _insertKey(self, char, pos, sel_start, sel_to, value, allowAutoSelect=False): """ Handles replacement of the character at the current insertion point.""" - dbg('MaskedEditMixin::_insertKey', "\'" + char + "\'", pos, sel_start, sel_to, '"%s"' % value, indent=1) +## dbg('MaskedEditMixin::_insertKey', "\'" + char + "\'", pos, sel_start, sel_to, '"%s"' % value, indent=1) text = self._eraseSelection(value) field = self._FindField(pos) @@ -4643,7 +4698,7 @@ class MaskedEditMixin: # adjustpos must have moved the position; make selection match: sel_start = sel_to = pos - dbg('field._insertRight?', field._insertRight) +## dbg('field._insertRight?', field._insertRight) if( field._insertRight # field allows right insert and ((sel_start, sel_to) == field._extent # and whole field selected or (sel_start == sel_to # or nothing selected @@ -4651,7 +4706,7 @@ class MaskedEditMixin: or (field._allowInsert # or field allows right-insert and sel_start < end # next to other char in field: and text[sel_start] != field._fillChar) ) ) ) ): - dbg('insertRight') +## dbg('insertRight') fstr = text[start:end] erasable_chars = [field._fillChar, ' '] @@ -4659,42 +4714,42 @@ class MaskedEditMixin: erasable_chars.append('0') erased = '' -## dbg("fstr[0]:'%s'" % fstr[0]) -## dbg('field_index:', field._index) -## dbg("fstr[0] in erasable_chars?", fstr[0] in erasable_chars) -## dbg("self._signOk and field._index == 0 and fstr[0] in ('-','(')?", +#### dbg("fstr[0]:'%s'" % fstr[0]) +#### dbg('field_index:', field._index) +#### dbg("fstr[0] in erasable_chars?", fstr[0] in erasable_chars) +#### dbg("self._signOk and field._index == 0 and fstr[0] in ('-','(')?", ## self._signOk and field._index == 0 and fstr[0] in ('-','(')) if fstr[0] in erasable_chars or (self._signOk and field._index == 0 and fstr[0] in ('-','(')): erased = fstr[0] -## dbg('value: "%s"' % text) -## dbg('fstr: "%s"' % fstr) -## dbg("erased: '%s'" % erased) +#### dbg('value: "%s"' % text) +#### dbg('fstr: "%s"' % fstr) +#### dbg("erased: '%s'" % erased) 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]) +## 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 += (field_len - len(fstr)) # move cursor right by deleted amount pos = sel_to - dbg('setting pos to:', pos) +## dbg('setting pos to:', pos) if field._padZero: fstr = '0' * (field_len - len(fstr)) + fstr else: fstr = fstr.rjust(field_len) # adjust the field accordingly - dbg('field str: "%s"' % fstr) +## 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) +## dbg('newtext: "%s"' % newtext) if self._signOk and field._index == 0: start -= 1 # account for sign position -## dbg('field._moveOnFieldFull?', field._moveOnFieldFull) -## dbg('len(fstr.lstrip()) == end-start?', len(fstr.lstrip()) == end-start) +#### dbg('field._moveOnFieldFull?', field._moveOnFieldFull) +#### dbg('len(fstr.lstrip()) == end-start?', len(fstr.lstrip()) == end-start) if( field._moveOnFieldFull and pos == end and len(fstr.lstrip()) == end-start): # if field now full newpos = self._findNextEntry(end) # go to next field @@ -4702,9 +4757,10 @@ class MaskedEditMixin: newpos = pos # else keep cursor at current position if not newtext: - dbg('not newtext') +## dbg('not newtext') if newpos != pos: - dbg('newpos:', newpos) +## dbg('newpos:', newpos) + pass if self._signOk and self._useParens: old_right_signpos = text.find(')') @@ -4713,10 +4769,10 @@ class MaskedEditMixin: field_len = end - start before = text[start:sel_start] after = text[sel_to:end].strip() -## dbg("current field:'%s'" % text[start:end]) -## dbg("before:'%s'" % before, "after:'%s'" % after) +#### dbg("current field:'%s'" % text[start:end]) +#### dbg("before:'%s'" % before, "after:'%s'" % after) new_len = len(before) + len(after) + 1 # (for inserted char) -## dbg('new_len:', new_len) +#### dbg('new_len:', new_len) if new_len < field_len: retained = after + self._template[end-(field_len-new_len):end] @@ -4726,7 +4782,7 @@ class MaskedEditMixin: retained = after left = text[0:start] + before -## dbg("left:'%s'" % left, "retained:'%s'" % retained) +#### dbg("left:'%s'" % left, "retained:'%s'" % retained) right = retained + text[end:] else: left = text[0:pos] @@ -4755,16 +4811,16 @@ class MaskedEditMixin: else: rstripped_text = newtext.rstrip() right_signpos = len(rstripped_text) - dbg('old_right_signpos:', old_right_signpos, 'right signpos now:', right_signpos) +## dbg('old_right_signpos:', old_right_signpos, 'right signpos now:', right_signpos) newtext = newtext[:right_signpos] + ')' + newtext[right_signpos+1:] if( field._insertRight # if insert-right field (but we didn't start at right edge) and field._moveOnFieldFull # and should move cursor when full and len(newtext[start:end].strip()) == end-start): # and field now full newpos = self._findNextEntry(end) # go to next field - dbg('newpos = nextentry =', newpos) +## dbg('newpos = nextentry =', newpos) else: - dbg('pos:', pos, 'newpos:', pos+1) +## dbg('pos:', pos, 'newpos:', pos+1) newpos = pos+1 @@ -4804,12 +4860,12 @@ class MaskedEditMixin: # adjust position to just after partial match in control: 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) +## 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) +## dbg('newtext: "%s"' % newtext, 'newpos:', newpos) +## dbg(indent=0) return newtext, newpos @@ -4823,7 +4879,7 @@ class MaskedEditMixin: preserve the correct selection when the focus event is not due to tab, we need to pull the following trick: """ - dbg('MaskedEditMixin::_OnFocus') +## dbg('MaskedEditMixin::_OnFocus') wx.CallAfter(self._fixSelection) event.Skip() self.Refresh() @@ -4835,41 +4891,42 @@ class MaskedEditMixin: current value of the control is a "valid value," and has the side effect of coloring the control appropriately. """ - dbg(suspend=1) - dbg('MaskedEditMixin::_CheckValid: candidate="%s"' % candidate, indent=1) +## dbg(suspend=1) +## dbg('MaskedEditMixin::_CheckValid: candidate="%s"' % candidate, indent=1) oldValid = self._valid if candidate is None: value = self._GetValue() else: value = candidate - dbg('value: "%s"' % value) +## dbg('value: "%s"' % value) oldvalue = value valid = True # assume True if not self.IsDefault(value) and self._isDate: ## Date type validation valid = self._validateDate(value) - dbg("valid date?", valid) +## dbg("valid date?", valid) elif not self.IsDefault(value) and self._isTime: valid = self._validateTime(value) - dbg("valid time?", valid) +## dbg("valid time?", valid) elif not self.IsDefault(value) and (self._isInt or self._isFloat): ## Numeric type valid = self._validateNumeric(value) - dbg("valid Number?", valid) +## dbg("valid Number?", valid) if valid: # and not self.IsDefault(value): ## generic validation accounts for IsDefault() ## valid so far; ensure also allowed by any list or regex provided: valid = self._validateGeneric(value) - dbg("valid value?", valid) +## dbg("valid value?", valid) - dbg('valid?', 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) +## dbg('validity changed: oldValid =',oldValid,'newvalid =', self._valid) +## dbg('oldvalue: "%s"' % oldvalue, 'newvalue: "%s"' % self._GetValue()) + pass +## dbg(indent=0, suspend=0) return valid @@ -4908,47 +4965,47 @@ class MaskedEditMixin: 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]) +## 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 # else... - dbg('number:', number) +## dbg('number:', number) if self._ctrl_constraints._hasRange: valid = self._ctrl_constraints._rangeLow <= number <= self._ctrl_constraints._rangeHigh else: valid = True groupcharpos = value.rfind(groupchar) if groupcharpos != -1: # group char present - dbg('groupchar found at', groupcharpos) +## dbg('groupchar found at', groupcharpos) if self._isFloat and groupcharpos > self._decimalpos: # 1st one found on right-hand side is past decimal point - dbg('groupchar in fraction; illegal') +## dbg('groupchar in fraction; illegal') valid = False elif self._isFloat: integer = value[:self._decimalpos].strip() else: integer = value.strip() - dbg("integer:'%s'" % integer) +## dbg("integer:'%s'" % integer) if integer[0] in ('-', '('): integer = integer[1:] if integer[-1] == ')': integer = integer[:-1] parts = integer.split(groupchar) - dbg('parts:', parts) +## dbg('parts:', parts) for i in range(len(parts)): if i == 0 and abs(int(parts[0])) > 999: - dbg('group 0 too long; illegal') +## dbg('group 0 too long; illegal') valid = False break elif i > 0 and (len(parts[i]) != 3 or ' ' in parts[i]): - dbg('group %i (%s) not right size; illegal' % (i, parts[i])) +## dbg('group %i (%s) not right size; illegal' % (i, parts[i])) valid = False break except ValueError: - dbg('value not a valid number') +## dbg('value not a valid number') valid = False return valid @@ -4957,12 +5014,12 @@ class MaskedEditMixin: """ Validate the current date value using the provided Regex filter. Generally used for character types.BufferType """ - dbg('MaskedEditMixin::_validateDate', indent=1) +## dbg('MaskedEditMixin::_validateDate', indent=1) if candidate is None: value = self._GetValue() else: value = candidate - dbg('value = "%s"' % value) +## dbg('value = "%s"' % value) text = self._adjustDate(value, force4digit_year=True) ## Fix the date up before validating it - dbg('text =', text) +## dbg('text =', text) valid = True # assume True until proven otherwise try: @@ -4977,19 +5034,19 @@ class MaskedEditMixin: year, month, day = getDateParts( datestr, self._datestyle) year = int(year) - dbg('self._dateExtent:', self._dateExtent) +## 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) +## dbg('year, month, day:', year, month, day) except ValueError: - dbg('cannot convert string to integer parts') +## dbg('cannot convert string to integer parts') valid = False except KeyError: - dbg('cannot convert string to integer month') +## dbg('cannot convert string to integer month') valid = False if valid: @@ -5001,12 +5058,12 @@ class MaskedEditMixin: else: month -= 1 try: - dbg("trying to create date from values day=%d, month=%d, year=%d" % (day,month,year)) +## dbg("trying to create date from values day=%d, month=%d, year=%d" % (day,month,year)) dateHandler = wx.DateTimeFromDMY(day,month,year) - dbg("succeeded") +## dbg("succeeded") dateOk = True except: - dbg('cannot convert string to valid date') +## dbg('cannot convert string to valid date') dateOk = False if not dateOk: valid = False @@ -5016,16 +5073,17 @@ class MaskedEditMixin: # so we eliminate them here: timeStr = text[self._dateExtent+1:].strip() ## time portion of the string if timeStr: - dbg('timeStr: "%s"' % 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') +## dbg('cannot convert string to valid time') + pass if valid: dbg('valid date') - dbg(indent=0) +## dbg(indent=0) return valid @@ -5033,40 +5091,41 @@ class MaskedEditMixin: """ Validate the current time value using the provided Regex filter. Generally used for character types.BufferType """ - dbg('MaskedEditMixin::_validateTime', indent=1) +## dbg('MaskedEditMixin::_validateTime', indent=1) # wxDateTime doesn't take kindly to leading/trailing spaces when parsing, # so we eliminate them here: if candidate is None: value = self._GetValue().strip() else: value = candidate.strip() - dbg('value = "%s"' % value) +## dbg('value = "%s"' % value) valid = True # assume True until proven otherwise dateHandler = wx.DateTime_Today() try: checkTime = dateHandler.ParseTime(value) - dbg('checkTime:', checkTime, 'len(value)', len(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') +## dbg('cannot convert string to valid time') + pass if valid: dbg('valid time') - dbg(indent=0) +## dbg(indent=0) return valid def _OnKillFocus(self,event): """ Handler for EVT_KILL_FOCUS event. """ - dbg('MaskedEditMixin::_OnKillFocus', 'isDate=',self._isDate, indent=1) +## dbg('MaskedEditMixin::_OnKillFocus', 'isDate=',self._isDate, indent=1) if self._mask and self._IsEditable(): self._AdjustField(self._GetInsertionPoint()) self._CheckValid() ## Call valid handler self._LostFocus() ## Provided for subclass use event.Skip() - dbg(indent=0) +## dbg(indent=0) def _fixSelection(self): @@ -5083,20 +5142,20 @@ class MaskedEditMixin: we can assume the cause, change the insertion point to the start of the control, and deselect. """ - dbg('MaskedEditMixin::_fixSelection', indent=1) +## dbg('MaskedEditMixin::_fixSelection', indent=1) if not self._mask or not self._IsEditable(): - dbg(indent=0) +## dbg(indent=0) return sel_start, sel_to = self._GetSelection() - dbg('sel_start, sel_to:', sel_start, sel_to, 'self.IsEmpty()?', self.IsEmpty()) +## dbg('sel_start, sel_to:', sel_start, sel_to, 'self.IsEmpty()?', self.IsEmpty()) if( sel_start == 0 and sel_to >= len( self._mask ) #(can be greater in numeric controls because of reserved space) and (not self._ctrl_constraints._autoSelect or self.IsEmpty() or self.IsDefault() ) ): # This isn't normally allowed, and so assume we got here by the new # "tab traversal" behavior, so we need to reset the selection # and insertion point: - dbg('entire text selected; resetting selection to start of control') +## dbg('entire text selected; resetting selection to start of control') self._goHome() field = self._FindField(self._GetInsertionPoint()) edit_start, edit_end = field._extent @@ -5116,25 +5175,26 @@ class MaskedEditMixin: edit_start, edit_end = integer._extent if integer._selectOnFieldEntry: - dbg('select on field entry:') +## dbg('select on field entry:') self._SetInsertionPoint(edit_start) self._SetSelection(edit_start, edit_end) elif integer._insertRight: - dbg('moving insertion point to end') +## 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') +## dbg('numeric ctrl is empty; start at beginning after sign') self._SetInsertionPoint(signpos+1) ## Move past minus sign space if signed 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') +## 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) +## dbg('sel_start, sel_to:', sel_start, sel_to, 'self._masklength:', self._masklength) + pass +## dbg(indent=0) def _Keypress(self,key): @@ -5176,11 +5236,11 @@ class MaskedEditMixin: derived control because the mixin functions can't override a method of a sibling class. """ - dbg("MaskedEditMixin::_Cut", indent=1) +## dbg("MaskedEditMixin::_Cut", indent=1) value = self._GetValue() - dbg('current value: "%s"' % value) +## dbg('current value: "%s"' % value) sel_start, sel_to = self._GetSelection() ## check for a range of selected text - dbg('selected text: "%s"' % value[sel_start:sel_to].strip()) +## dbg('selected text: "%s"' % value[sel_start:sel_to].strip()) do = wxTextDataObject() do.SetText(value[sel_start:sel_to].strip()) wxTheClipboard.Open() @@ -5189,7 +5249,7 @@ class MaskedEditMixin: if sel_to - sel_start != 0: self._OnErase() - dbg(indent=0) +## dbg(indent=0) # WS Note: overriding Copy is no longer necessary given that you @@ -5233,32 +5293,32 @@ class MaskedEditMixin: returns validity, replacement text, and extent of paste in template. """ - dbg(suspend=1) - dbg('MaskedEditMixin::_validatePaste("%(paste_text)s", %(sel_start)d, %(sel_to)d), raise_on_invalid? %(raise_on_invalid)d' % locals(), indent=1) +## dbg(suspend=1) +## dbg('MaskedEditMixin::_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) +## dbg('sel_to - sel_start:', maxlength) if maxlength == 0: maxlength = self._masklength - sel_start item = 'control' else: item = 'selection' - dbg('maxlength:', maxlength) +## dbg('maxlength:', maxlength) length_considered = len(paste_text) if length_considered > maxlength: - dbg('paste text will not fit into the %s:' % item, indent=0) +## dbg('paste text will not fit into the %s:' % item, indent=0) if raise_on_invalid: - dbg(indent=0, suspend=0) +## 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) +## dbg(indent=0, suspend=0) return False, None, None text = self._template - dbg('length_considered:', length_considered) +## dbg('length_considered:', length_considered) valid_paste = True replacement_text = "" @@ -5267,7 +5327,7 @@ class MaskedEditMixin: while valid_paste and i < length_considered and replace_to < self._masklength: if paste_text[i:] == self._template[replace_to:length_considered]: # remainder of paste matches template; skip char-by-char analysis - dbg('remainder paste_text[%d:] (%s) matches template[%d:%d]' % (i, paste_text[i:], 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 @@ -5278,12 +5338,12 @@ class MaskedEditMixin: 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)) +## 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+'"') +## dbg("not template(%(replace_to)d) and charAllowed('%(char)s',%(replace_to)d)" % locals()) +## dbg("replacement_text:", '"'+replacement_text+'"') i += 1 replace_to += 1 elif( char == self._template[replace_to] @@ -5291,8 +5351,8 @@ class MaskedEditMixin: ( (i == 0 and (char == '-' or (self._useParens and char == '('))) or (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+'"') +## dbg("'%(char)s' == template(%(replace_to)d)" % locals()) +## dbg("replacement_text:", '"'+replacement_text+'"') i += 1 replace_to += 1 else: @@ -5301,24 +5361,25 @@ class MaskedEditMixin: valid_paste = False else: replacement_text += self._template[replace_to:next_entry] - dbg("skipping template; next_entry =", next_entry) - dbg("replacement_text:", '"'+replacement_text+'"') +## dbg("skipping template; next_entry =", next_entry) +## dbg("replacement_text:", '"'+replacement_text+'"') replace_to = next_entry # so next_entry will be considered on next loop if not valid_paste and raise_on_invalid: - dbg('raising exception', indent=0, suspend=0) +## 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) +## 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) +## dbg('valid_paste?', valid_paste) if valid_paste: - dbg('replacement_text: "%s"' % replacement_text, 'replace to:', replace_to) - dbg(indent=0, suspend=0) +## dbg('replacement_text: "%s"' % replacement_text, 'replace to:', replace_to) + pass +## dbg(indent=0, suspend=0) return valid_paste, replacement_text, replace_to @@ -5330,17 +5391,17 @@ class MaskedEditMixin: derived control because the mixin functions can't override a method of a sibling class. """ - dbg('MaskedEditMixin::_Paste (value = "%s")' % value, indent=1) +## dbg('MaskedEditMixin::_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) +## dbg('paste text: "%s"' % paste_text) # (conversion will raise ValueError if paste isn't legal) sel_start, sel_to = self._GetSelection() - dbg('selection:', (sel_start, sel_to)) +## dbg('selection:', (sel_start, sel_to)) # special case: handle allowInsert fields properly field = self._FindField(sel_start) @@ -5349,7 +5410,7 @@ class MaskedEditMixin: if field._allowInsert and sel_to <= edit_end and sel_start + len(paste_text) < edit_end: new_pos = sel_start + len(paste_text) # store for subsequent positioning 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) +## dbg('paste within insertable field; adjusted paste_text: "%s"' % paste_text, 'end:', edit_end) sel_to = sel_start + len(paste_text) # Another special case: paste won't fit, but it's a right-insert field where entire @@ -5365,7 +5426,7 @@ class MaskedEditMixin: 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)) +## dbg('expanded selection to:', (sel_start, sel_to)) # another special case: deal with signed values properly: @@ -5376,10 +5437,10 @@ class MaskedEditMixin: paste_signpos = paste_text.find('(') # if paste text will result in signed value: -## dbg('paste_signpos != -1?', paste_signpos != -1) -## dbg('sel_start:', sel_start, 'signpos:', signpos) -## dbg('field._insertRight?', field._insertRight) -## dbg('sel_start - len(paste_text) >= signpos?', sel_start - len(paste_text) <= signpos) +#### dbg('paste_signpos != -1?', paste_signpos != -1) +#### dbg('sel_start:', sel_start, 'signpos:', signpos) +#### dbg('field._insertRight?', field._insertRight) +#### dbg('sel_start - len(paste_text) >= signpos?', sel_start - len(paste_text) <= signpos) if paste_signpos != -1 and (sel_start <= signpos or (field._insertRight and sel_start - len(paste_text) <= signpos)): signed = True @@ -5387,32 +5448,32 @@ class MaskedEditMixin: signed = False # remove "sign" from paste text, so we can auto-adjust for sign type after paste: paste_text = paste_text.replace('-', ' ').replace('(',' ').replace(')','') - dbg('unsigned paste text: "%s"' % paste_text) +## dbg('unsigned paste text: "%s"' % paste_text) else: signed = False # another special case: deal with insert-right fields when selection is empty and # cursor is at end of field: -## dbg('field._insertRight?', field._insertRight) -## dbg('sel_start == edit_end?', sel_start == edit_end) -## dbg('sel_start', sel_start, 'sel_to', sel_to) +#### dbg('field._insertRight?', field._insertRight) +#### dbg('sel_start == edit_end?', sel_start == edit_end) +#### dbg('sel_start', sel_start, 'sel_to', sel_to) 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)) +## 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) +## 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;') +## dbg('paste text not legal for the selection or portion of the control following the cursor;') if not wx.Validator_IsSilent(): wx.Bell() - dbg(indent=0) +## dbg(indent=0) return False # else... text = self._eraseSelection() @@ -5430,9 +5491,11 @@ class MaskedEditMixin: if not self._isNeg: self._isNeg = 1 - dbg("new_text:", '"'+new_text+'"') +## dbg("new_text:", '"'+new_text+'"') if not just_return_value: + if new_text != self._GetValue(): + self.modified = True if new_text == '': self.ClearValue() else: @@ -5441,22 +5504,22 @@ class MaskedEditMixin: new_pos = sel_start + len(replacement_text) wx.CallAfter(self._SetInsertionPoint, new_pos) else: - dbg(indent=0) +## dbg(indent=0) return new_text elif just_return_value: - dbg(indent=0) +## dbg(indent=0) return self._GetValue() - dbg(indent=0) +## dbg(indent=0) def _Undo(self): """ Provides an Undo() method in base controls. """ - dbg("MaskedEditMixin::_Undo", indent=1) +## dbg("MaskedEditMixin::_Undo", indent=1) value = self._GetValue() prev = self._prevValue - dbg('current value: "%s"' % value) - dbg('previous value: "%s"' % prev) +## dbg('current value: "%s"' % value) +## dbg('previous value: "%s"' % prev) if prev is None: - dbg('no previous value', indent=0) +## dbg('no previous value', indent=0) return elif value != prev: @@ -5493,24 +5556,24 @@ class MaskedEditMixin: # Determine where they stop differing in "undo" result: 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] ) +## 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: # no match found; select to end 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])) +## dbg("%7s value[%d:%d] (%s) prev[%d:%d] (%s)" % (op, i1, i2, value[i1:i2], j1, j2, prev[j1:j2])) + pass diff_found = False # look backward through operations needed to produce "previous" value; # first change wins: 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]) +## 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)) +## dbg('insert found: selection =>', (j1, j2)) sel_start = j1 sel_to = j2 diff_found = True @@ -5524,11 +5587,11 @@ class MaskedEditMixin: else: sel_start = i1 sel_to = j1 - dbg('delete found: selection =>', (sel_start, sel_to)) +## dbg('delete found: selection =>', (sel_start, sel_to)) diff_found = True break elif op == 'replace': - dbg('replace found: selection =>', (j1, j2)) +## dbg('replace found: selection =>', (j1, j2)) sel_start = j1 sel_to = j2 diff_found = True @@ -5543,17 +5606,17 @@ class MaskedEditMixin: if op == 'equal': continue elif op == 'replace': - dbg('setting sel_start to', i1) +## dbg('setting sel_start to', i1) sel_start = i1 break elif op == 'insert' and not value[i1:i2]: - dbg('forward %s found' % op) +## dbg('forward %s found' % op) if prev[j1:j2].strip(): - dbg('item to insert non-empty; setting sel_start to', j1) +## 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) +## dbg('setting sel_start to inserted space:', j1) sel_start = j1 break elif op == 'delete' and field._insertRight and not value[i1:i2].lstrip(): @@ -5564,7 +5627,7 @@ class MaskedEditMixin: if not diff_found: - dbg('no insert,delete or replace found (!)') +## dbg('no insert,delete or replace found (!)') # do "left-insert"-centric processing of difference based on l.c.s.: if i == j and j != sel_start: # match starts after start of selection sel_to = sel_start + (j-sel_start) # select to start of match @@ -5608,7 +5671,7 @@ class MaskedEditMixin: # To get all this right, we use the previous selection recorded to help us... if (sel_start, sel_to) != self._prevSelection: - dbg('calculated selection', (sel_start, sel_to), "doesn't match previous", 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) @@ -5627,8 +5690,8 @@ class MaskedEditMixin: 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) +## 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: # old selection was bigger; trust it: @@ -5639,7 +5702,7 @@ class MaskedEditMixin: and sel_to == len(self._template) ): # and calculated selection goes to end of control 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] ) +## 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: # difflib must not have optimized opcodes properly; sel_to = j @@ -5662,9 +5725,9 @@ class MaskedEditMixin: 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]) +## 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 calculated selection spans characters, and same characters # "before" the previous insertion point are present there as well, @@ -5675,14 +5738,15 @@ class MaskedEditMixin: 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) +## 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) +## dbg('no difference between previous value') + pass +## dbg(indent=0) def _OnClear(self, event): @@ -5691,7 +5755,7 @@ class MaskedEditMixin: def _OnContextMenu(self, event): - dbg('MaskedEditMixin::OnContextMenu()', indent=1) +## dbg('MaskedEditMixin::OnContextMenu()', indent=1) menu = wxMenu() menu.Append(wxID_UNDO, "Undo", "") menu.AppendSeparator() @@ -5722,7 +5786,7 @@ class MaskedEditMixin: self.PopupMenu(menu, event.GetPosition()) menu.Destroy() self._contextMenu = None - dbg(indent=0) +## dbg(indent=0) def _UndoUpdateUI(self, event): if self._prevValue is None or self._prevValue == self._curValue: @@ -5731,13 +5795,73 @@ class MaskedEditMixin: self._contextMenu.Enable(wxID_UNDO, True) -## ---------- ---------- ---------- ---------- ---------- ---------- ---------- + def _OnCtrlParametersChanged(self): + """ + Overridable function to allow derived classes to take action as a + result of parameter changes prior to possibly changing the value + of the control. + """ + pass -class MaskedTextCtrl( wx.TextCtrl, MaskedEditMixin ): + ## ---------- ---------- ---------- ---------- ---------- ---------- ---------- +# ## TRICKY BIT: to avoid a ton of boiler-plate, and to +# ## automate the getter/setter generation for each valid +# ## control parameter so we never forget to add the +# ## functions when adding parameters, this loop +# ## programmatically adds them to the class: +# ## (This makes it easier for Designers like Boa to +# ## deal with masked controls.) +# +# ## To further complicate matters, this is done with an +# ## extra level of inheritance, so that "general" classes like +# ## MaskedTextCtrl can have all possible attributes, +# ## while derived classes, like TimeCtrl and MaskedNumCtrl +# ## can prevent exposure of those optional attributes of their base +# ## class that do not make sense for their derivation. Therefore, +# ## we define +# ## BaseMaskedTextCtrl(TextCtrl, MaskedEditMixin) +# ## and +# ## MaskedTextCtrl(BaseMaskedTextCtrl, MaskedEditAccessorsMixin). +# ## +# ## This allows us to then derive: +# ## MaskedNumCtrl( BaseMaskedTextCtrl ) +# ## +# ## and not have to expose all the same accessor functions for the +# ## derived control when they don't all make sense for it. +# ## +class MaskedEditAccessorsMixin: + + # Define the default set of attributes exposed by the most generic masked controls: + exposed_basectrl_params = MaskedEditMixin.valid_ctrl_params.keys() + Field.valid_params.keys() + exposed_basectrl_params.remove('index') + exposed_basectrl_params.remove('extent') + exposed_basectrl_params.remove('foregroundColour') # (base class already has this) + + for param in exposed_basectrl_params: + 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: + # add non-british spellings, for backward-compatibility + 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)) + + + + +class BaseMaskedTextCtrl( wx.TextCtrl, MaskedEditMixin ): """ This is the primary derivation from MaskedEditMixin. It provides a general masked text control that can be configured with different - masks. + masks. It's actually a "base masked textCtrl", so that the + MaskedTextCtrl class can be derived from it, and add those + accessor functions to it that are appropriate to the general class, + whilst other classes can derive from BaseMaskedTextCtrl, and + only define those accessor functions that are appropriate for + those derivations. """ def __init__( self, parent, id=-1, value = '', @@ -5756,6 +5880,7 @@ class MaskedTextCtrl( wx.TextCtrl, MaskedEditMixin ): self.controlInitialized = True MaskedEditMixin.__init__( self, name, **kwargs ) + self._SetInitialValue(value) if setupEventHandling: @@ -5771,7 +5896,7 @@ class MaskedTextCtrl( wx.TextCtrl, MaskedEditMixin ): def __repr__(self): - return "" % self.GetValue() + return "" % self.GetValue() def _GetSelection(self): @@ -5786,14 +5911,14 @@ class MaskedTextCtrl( wx.TextCtrl, MaskedEditMixin ): Allow mixin to set the text selection of this control. REQUIRED by any class derived from MaskedEditMixin. """ -## dbg("MaskedTextCtrl::_SetSelection(%(sel_start)d, %(sel_to)d)" % locals()) +#### dbg("MaskedTextCtrl::_SetSelection(%(sel_start)d, %(sel_to)d)" % locals()) return self.SetSelection( sel_start, sel_to ) def SetSelection(self, sel_start, sel_to): """ This is just for debugging... """ - dbg("MaskedTextCtrl::SetSelection(%(sel_start)d, %(sel_to)d)" % locals()) +## dbg("MaskedTextCtrl::SetSelection(%(sel_start)d, %(sel_to)d)" % locals()) wx.TextCtrl.SetSelection(self, sel_start, sel_to) @@ -5801,14 +5926,14 @@ class MaskedTextCtrl( wx.TextCtrl, MaskedEditMixin ): return self.GetInsertionPoint() def _SetInsertionPoint(self, pos): -## dbg("MaskedTextCtrl::_SetInsertionPoint(%(pos)d)" % locals()) +#### dbg("MaskedTextCtrl::_SetInsertionPoint(%(pos)d)" % locals()) self.SetInsertionPoint(pos) def SetInsertionPoint(self, pos): """ This is just for debugging... """ - dbg("MaskedTextCtrl::SetInsertionPoint(%(pos)d)" % locals()) +## dbg("MaskedTextCtrl::SetInsertionPoint(%(pos)d)" % locals()) wx.TextCtrl.SetInsertionPoint(self, pos) @@ -5824,12 +5949,12 @@ class MaskedTextCtrl( wx.TextCtrl, MaskedEditMixin ): Allow mixin to set the raw value of the control with this function. REQUIRED by any class derived from MaskedEditMixin. """ - dbg('MaskedTextCtrl::_SetValue("%(value)s")' % locals(), indent=1) +## dbg('MaskedTextCtrl::_SetValue("%(value)s")' % locals(), indent=1) # Record current selection and insertion point, for undo self._prevSelection = self._GetSelection() self._prevInsertionPoint = self._GetInsertionPoint() wx.TextCtrl.SetValue(self, value) - dbg(indent=0) +## dbg(indent=0) def SetValue(self, value): """ @@ -5838,7 +5963,7 @@ class MaskedTextCtrl( wx.TextCtrl, MaskedEditMixin ): masked control. NOTE: this must be done in the class derived from the base wx control. """ - dbg('MaskedTextCtrl::SetValue = "%s"' % value, indent=1) +## dbg('MaskedTextCtrl::SetValue = "%s"' % value, indent=1) if not self._mask: wx.TextCtrl.SetValue(self, value) # revert to base control behavior @@ -5858,12 +5983,12 @@ class MaskedTextCtrl( wx.TextCtrl, MaskedEditMixin ): and (self._isFloat or self._isInt) # and it's a numeric control and self._ctrl_constraints._alignRight ): # and it's a right-aligned control - dbg('len(value)', len(value), ' < self._masklength', self._masklength) +## dbg('len(value)', len(value), ' < self._masklength', self._masklength) # try to intelligently "pad out" the value to the right size: value = self._template[0:self._masklength - len(value)] + value if self._isFloat and value.find('.') == -1: value = value[1:] - dbg('padded value = "%s"' % value) +## dbg('padded value = "%s"' % value) # make SetValue behave the same as if you had typed the value in: try: @@ -5882,22 +6007,23 @@ class MaskedTextCtrl( wx.TextCtrl, MaskedEditMixin ): dateparts = value.split(' ') dateparts[0] = self._adjustDate(dateparts[0], fixcentury=True) value = string.join(dateparts, ' ') - dbg('adjusted value: "%s"' % value) +## dbg('adjusted value: "%s"' % value) value = self._Paste(value, raise_on_invalid=True, just_return_value=True) else: - dbg('exception thrown', indent=0) +## dbg('exception thrown', indent=0) raise - self._SetValue(value) -## dbg('queuing insertion after .SetValue', self._masklength) + self._SetValue(value) # note: to preserve similar capability, .SetValue() + # does not change IsModified() +#### dbg('queuing insertion after .SetValue', self._masklength) wx.CallAfter(self._SetInsertionPoint, self._masklength) wx.CallAfter(self._SetSelection, self._masklength, self._masklength) - dbg(indent=0) +## dbg(indent=0) def Clear(self): """ Blanks the current control value by replacing it with the default value.""" - dbg("MaskedTextCtrl::Clear - value reset to default value (template)") +## dbg("MaskedTextCtrl::Clear - value reset to default value (template)") if self._mask: self.ClearValue() else: @@ -5909,9 +6035,9 @@ class MaskedTextCtrl( wx.TextCtrl, MaskedEditMixin ): Allow mixin to refresh the base control with this function. REQUIRED by any class derived from MaskedEditMixin. """ - dbg('MaskedTextCtrl::_Refresh', indent=1) +## dbg('MaskedTextCtrl::_Refresh', indent=1) wx.TextCtrl.Refresh(self) - dbg(indent=0) +## dbg(indent=0) def Refresh(self): @@ -5920,10 +6046,10 @@ class MaskedTextCtrl( wx.TextCtrl, MaskedEditMixin ): validate the contents of the masked control as it refreshes. NOTE: this must be done in the class derived from the base wx control. """ - dbg('MaskedTextCtrl::Refresh', indent=1) +## dbg('MaskedTextCtrl::Refresh', indent=1) self._CheckValid() self._Refresh() - dbg(indent=0) +## dbg(indent=0) def _IsEditable(self): @@ -5989,6 +6115,16 @@ class MaskedTextCtrl( wx.TextCtrl, MaskedEditMixin ): return self._calcSize(size) +class MaskedTextCtrl( BaseMaskedTextCtrl, MaskedEditAccessorsMixin ): + """ + This extra level of inheritance allows us to add the generic set of + masked edit parameters only to this class while allowing other + classes to derive from the "base" masked text control, and provide + a smaller set of valid accessor functions. + """ + pass + + ## ---------- ---------- ---------- ---------- ---------- ---------- ---------- ## Because calling SetSelection programmatically does not fire EVT_COMBOBOX ## events, we have to do it ourselves when we auto-complete. @@ -6005,7 +6141,7 @@ class MaskedComboBoxSelectEvent(wx.PyCommandEvent): return self.__selection -class MaskedComboBox( wx.ComboBox, MaskedEditMixin ): +class BaseMaskedComboBox( wx.ComboBox, MaskedEditMixin ): """ This masked edit control adds the ability to use a masked input on a combobox, and do auto-complete of such values. @@ -6033,8 +6169,9 @@ class MaskedComboBox( wx.ComboBox, MaskedEditMixin ): kwargs['compareNoCase'] = True MaskedEditMixin.__init__( self, name, **kwargs ) + self._choices = self._ctrl_constraints._choices - dbg('self._choices:', self._choices) +## dbg('self._choices:', self._choices) if self._ctrl_constraints._alignRight: choices = [choice.rjust(self._masklength) for choice in choices] @@ -6170,7 +6307,7 @@ class MaskedComboBox( wx.ComboBox, MaskedEditMixin ): and self._ctrl_constraints._alignRight ): # and it's a right-aligned control # try to intelligently "pad out" the value to the right size: value = self._template[0:self._masklength - len(value)] + value - dbg('padded value = "%s"' % value) +## dbg('padded value = "%s"' % value) # For wxComboBox, ensure that values are properly padded so that # if varying length choices are supplied, they always show up @@ -6199,13 +6336,13 @@ class MaskedComboBox( wx.ComboBox, MaskedEditMixin ): dateparts = value.split(' ') dateparts[0] = self._adjustDate(dateparts[0], fixcentury=True) value = string.join(dateparts, ' ') - dbg('adjusted value: "%s"' % value) +## dbg('adjusted value: "%s"' % value) value = self._Paste(value, raise_on_invalid=True, just_return_value=True) else: raise self._SetValue(value) -## dbg('queuing insertion after .SetValue', self._masklength) +#### dbg('queuing insertion after .SetValue', self._masklength) wx.CallAfter(self._SetInsertionPoint, self._masklength) wx.CallAfter(self._SetSelection, self._masklength, self._masklength) @@ -6302,7 +6439,7 @@ class MaskedComboBox( wx.ComboBox, MaskedEditMixin ): choice = choice.ljust(self._masklength) if self._ctrl_constraints._fillChar != ' ': choice = choice.replace(' ', self._fillChar) - dbg('updated choice:', choice) +## dbg('updated choice:', choice) self._ctrl_constraints._compareChoices.append(compareChoice) @@ -6331,14 +6468,12 @@ class MaskedComboBox( wx.ComboBox, MaskedEditMixin ): wx.ComboBox.Clear(self) - def SetCtrlParameters( self, **kwargs ): + def _OnCtrlParametersChanged(self): """ - Override mixin's default SetCtrlParameters to detect changes in choice list, so + Override mixin's default OnCtrlParametersChanged to detect changes in choice list, so we can update the base control: """ - MaskedEditMixin.SetCtrlParameters(self, **kwargs ) - if( self.controlInitialized - and (kwargs.has_key('choices') or self._choices != self._ctrl_constraints._choices) ): + if self.controlInitialized and self._choices != self._ctrl_constraints._choices: wx.ComboBox.Clear(self) self._choices = self._ctrl_constraints._choices for choice in self._choices: @@ -6352,25 +6487,25 @@ class MaskedComboBox( wx.ComboBox, MaskedEditMixin ): works, but has the nasty side effect of generating lots of intermediate events. """ - dbg(suspend=1) # turn off debugging around this function - dbg('MaskedComboBox::GetMark', indent=1) +## dbg(suspend=1) # turn off debugging around this function +## dbg('MaskedComboBox::GetMark', indent=1) if self.__readonly: - dbg(indent=0) +## dbg(indent=0) return 0, 0 # no selection possible for editing ## sel_start, sel_to = wxComboBox.GetMark(self) # what I'd *like* to have! sel_start = sel_to = self.GetInsertionPoint() - dbg("current sel_start:", sel_start) +## dbg("current sel_start:", sel_start) value = self.GetValue() - dbg('value: "%s"' % value) +## dbg('value: "%s"' % value) self._ignoreChange = True # tell _OnTextChange() to ignore next event (if any) wx.ComboBox.Cut(self) newvalue = self.GetValue() - dbg("value after Cut operation:", newvalue) +## dbg("value after Cut operation:", newvalue) if newvalue != value: # something was selected; calculate extent - dbg("something selected") +## dbg("something selected") sel_to = sel_start + len(value) - len(newvalue) wx.ComboBox.SetValue(self, value) # restore original value and selection (still ignoring change) wx.ComboBox.SetInsertionPoint(self, sel_start) @@ -6378,7 +6513,7 @@ class MaskedComboBox( wx.ComboBox, MaskedEditMixin ): self._ignoreChange = False # tell _OnTextChange() to pay attn again - dbg('computed selection:', sel_start, sel_to, indent=0, suspend=0) +## dbg('computed selection:', sel_start, sel_to, indent=0, suspend=0) return sel_start, sel_to @@ -6387,7 +6522,7 @@ class MaskedComboBox( wx.ComboBox, MaskedEditMixin ): Necessary for bookkeeping on choice selection, to keep current value current. """ - dbg('MaskedComboBox::SetSelection(%d)' % index) +## dbg('MaskedComboBox::SetSelection(%d)' % index) if self._mask: self._prevValue = self._curValue self._curValue = self._choices[index] @@ -6415,7 +6550,7 @@ class MaskedComboBox( wx.ComboBox, MaskedEditMixin ): on the text of the control somehow interferes with the combobox's selection mechanism for the arrow keys. """ - dbg('MaskedComboBox::OnSelectChoice', indent=1) +## dbg('MaskedComboBox::OnSelectChoice', indent=1) if not self._mask: event.Skip() @@ -6437,7 +6572,7 @@ class MaskedComboBox( wx.ComboBox, MaskedEditMixin ): self._ctrl_constraints._compareNoCase, current_index = self._ctrl_constraints._autoCompleteIndex) if match_index is not None: - dbg('setting selection to', match_index) +## dbg('setting selection to', match_index) # issue appropriate event to outside: self._OnAutoSelect(self._ctrl_constraints, match_index=match_index) self._CheckValid() @@ -6446,15 +6581,15 @@ class MaskedComboBox( wx.ComboBox, MaskedEditMixin ): pos = self._adjustPos(self._GetInsertionPoint(), event.GetKeyCode()) field = self._FindField(pos) if self.IsEmpty() or not field._hasList: - dbg('selecting 1st value in list') +## dbg('selecting 1st value in list') self._OnAutoSelect(self._ctrl_constraints, match_index=0) self._CheckValid() keep_processing = False else: # attempt field-level auto-complete - dbg(indent=0) +## dbg(indent=0) keep_processing = self._OnAutoCompleteField(event) - dbg('keep processing?', keep_processing, indent=0) +## dbg('keep processing?', keep_processing, indent=0) return keep_processing @@ -6463,17 +6598,17 @@ class MaskedComboBox( wx.ComboBox, MaskedEditMixin ): Override mixin (empty) autocomplete handler, so that autocompletion causes combobox to update appropriately. """ - dbg('MaskedComboBox::OnAutoSelect', field._index, indent=1) +## dbg('MaskedComboBox::OnAutoSelect', field._index, indent=1) ## field._autoCompleteIndex = match_index if field == self._ctrl_constraints: self.SetSelection(match_index) - dbg('issuing combo selection event') +## dbg('issuing combo selection event') self.GetEventHandler().ProcessEvent( MaskedComboBoxSelectEvent( self.GetId(), match_index, self ) ) self._CheckValid() - dbg('field._autoCompleteIndex:', match_index) - dbg('self.GetSelection():', self.GetSelection()) - dbg(indent=0) +## dbg('field._autoCompleteIndex:', match_index) +## dbg('self.GetSelection():', self.GetSelection()) +## dbg(indent=0) def _OnReturn(self, event): @@ -6486,25 +6621,75 @@ class MaskedComboBox( wx.ComboBox, MaskedEditMixin ): programmatic wxComboBox.SetSelection() call to pick the appropriate item in the list. (and then do the usual OnReturn bit.) """ - dbg('MaskedComboBox::OnReturn', indent=1) - dbg('current value: "%s"' % self.GetValue(), 'current index:', self.GetSelection()) +## dbg('MaskedComboBox::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: wx.CallAfter(self.SetSelection, self._ctrl_constraints._autoCompleteIndex) event.m_keyCode = wx.WXK_TAB event.Skip() - dbg(indent=0) +## dbg(indent=0) + + +class MaskedComboBox( BaseMaskedComboBox, MaskedEditAccessorsMixin ): + """ + This extra level of inheritance allows us to add the generic set of + masked edit parameters only to this class while allowing other + classes to derive from the "base" masked combobox control, and provide + a smaller set of valid accessor functions. + """ + pass ## ---------- ---------- ---------- ---------- ---------- ---------- ---------- -class IpAddrCtrl( MaskedTextCtrl ): +class IpAddrCtrlAccessorsMixin: + # Define IpAddrCtrl's list of attributes having their own + # Get/Set functions, exposing only those that make sense for + # an IP address control. + + exposed_basectrl_params = ( + 'fields', + 'retainFieldValidation', + 'formatcodes', + 'fillChar', + 'defaultValue', + 'description', + + 'useFixedWidthFont', + 'signedForegroundColour', + 'emptyBackgroundColour', + 'validBackgroundColour', + 'invalidBackgroundColour', + + 'emptyInvalid', + 'validFunc', + 'validRequired', + ) + + for param in exposed_basectrl_params: + 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: + # add non-british spellings, for backward-compatibility + 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)) + + +class IpAddrCtrl( BaseMaskedTextCtrl, IpAddrCtrlAccessorsMixin ): """ This class is a particular type of MaskedTextCtrl that accepts and understands the semantics of IP addresses, reformats input as you move from field to field, and accepts '.' as a navigation character, so that typing an IP address can be done naturally. """ + + + def __init__( self, parent, id=-1, value = '', pos = wx.DefaultPosition, size = wx.DefaultSize, @@ -6522,7 +6707,7 @@ class IpAddrCtrl( MaskedTextCtrl ): 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}" - MaskedTextCtrl.__init__( + BaseMaskedTextCtrl.__init__( self, parent, id=id, value = value, pos=pos, size=size, style = style, @@ -6531,6 +6716,7 @@ class IpAddrCtrl( MaskedTextCtrl ): setupEventHandling = setupEventHandling, **kwargs) + # set up individual field parameters as well: field_params = {} field_params['validRegex'] = "( | \d| \d |\d | \d\d|\d\d |\d \d|(1\d\d|2[0-4]\d|25[0-5]))" @@ -6549,7 +6735,7 @@ class IpAddrCtrl( MaskedTextCtrl ): def OnDot(self, event): - dbg('IpAddrCtrl::OnDot', indent=1) +## dbg('IpAddrCtrl::OnDot', indent=1) pos = self._adjustPos(self._GetInsertionPoint(), event.GetKeyCode()) oldvalue = self.GetValue() edit_start, edit_end, slice = self._FindFieldExtent(pos, getslice=True) @@ -6560,26 +6746,26 @@ class IpAddrCtrl( MaskedTextCtrl ): newvalue = oldvalue[:pos] + ' ' * (edit_end - pos) + oldvalue[edit_end:] self._SetValue(newvalue) self._SetInsertionPoint(pos) - dbg(indent=0) +## dbg(indent=0) return self._OnChangeField(event) def GetAddress(self): - value = MaskedTextCtrl.GetValue(self) + value = BaseMaskedTextCtrl.GetValue(self) return value.replace(' ','') # remove spaces from the value def _OnCtrl_S(self, event): - dbg("IpAddrCtrl::_OnCtrl_S") +## dbg("IpAddrCtrl::_OnCtrl_S") if self._demo: print "value:", self.GetAddress() return False def SetValue(self, value): - dbg('IpAddrCtrl::SetValue(%s)' % str(value), indent=1) +## dbg('IpAddrCtrl::SetValue(%s)' % str(value), indent=1) if type(value) not in (types.StringType, types.UnicodeType): - dbg(indent=0) +## dbg(indent=0) raise ValueError('%s must be a string', str(value)) bValid = True # assume True @@ -6609,13 +6795,13 @@ class IpAddrCtrl( MaskedTextCtrl ): parts[i] = ' ' # convert empty field to 3-char length if not bValid: - dbg(indent=0) +## 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) +## dbg('parts:', parts) value = string.join(parts, '.') - MaskedTextCtrl.SetValue(self, value) - dbg(indent=0) + BaseMaskedTextCtrl.SetValue(self, value) +## dbg(indent=0) ## ---------- ---------- ---------- ---------- ---------- ---------- ---------- @@ -6929,7 +7115,7 @@ To see a great example of validations in action, try entering a bad email addres ("US Date + Time","USDATETIMEMMDDYYYY/HHMM"), ("US Date MMDDYYYY","USDATEMMDDYYYY/"), ("Time (with seconds)","TIMEHHMMSS"), - ("Military Time\n(without seconds)","MILTIMEHHMM"), + ("Military Time\n(without seconds)","24HRTIMEHHMM"), ("Social Sec#","USSOCIALSEC"), ("Credit Card","CREDITCARD"), ("Expiration MM/YY","EXPDATEMMYY"), @@ -7013,7 +7199,7 @@ i=1 ## ## 3. WS: Controls on wxPanels don't seem to pass Shift-WXK_TAB to their ## EVT_KEY_DOWN or EVT_CHAR event handlers. Until this is fixed in -## wxWindows, shift-tab won't take you backwards through the fields of +## wxWidgets, shift-tab won't take you backwards through the fields of ## a MaskedTextCtrl like it should. Until then Shifted arrow keys will ## work like shift-tab and tab ought to. ## @@ -7033,6 +7219,28 @@ i=1 ## CHANGELOG: ## ==================== +## Version 1.5 +## (Reported) bugs fixed: +## 1. Crash ensues if you attempt to change the mask of a read-only +## MaskedComboBox after initial construction. +## 2. Changed strategy of defining Get/Set property functions so that +## these are now generated dynamically at runtime, rather than as +## part of the class definition. (This makes it possible to have +## more general base classes that have many more options for configuration +## without requiring that derivations support the same options.) +## 3. Fixed IsModified for _Paste() and _OnErase(). +## +## Enhancements: +## 1. Fixed "attribute function inheritance," since base control is more +## generic than subsequent derivations, not all property functions of a +## generic control should be exposed in those derivations. New strategy +## uses base control classes (eg. BaseMaskedTextCtrl) that should be +## used to derive new class types, and mixed with their own mixins to +## only expose those attributes from the generic masked controls that +## make sense for the derivation. (This makes Boa happier.) +## 2. Renamed (with b-c) MILTIME autoformats to 24HRTIME, so as to be less +## "parochial." +## ## Version 1.4 ## (Reported) bugs fixed: ## 1. Right-click menu allowed "cut" operation that destroyed mask @@ -7312,7 +7520,7 @@ i=1 ## (i.e. disallow empty values if True). ## ## Version 0.0.5 -## 1. get_plainValue method renamed to GetPlainValue following the wxWindows +## 1. get_plainValue method renamed to GetPlainValue following the wxWidgets ## StudlyCaps(tm) standard (thanks Paul Moore). ;) ## 2. New format code 'F' causes the control to auto-fit (auto-size) itself ## based on the length of the mask template. diff --git a/wxPython/wx/lib/maskednumctrl.py b/wxPython/wx/lib/maskednumctrl.py index 138a88adbe..9a3d234793 100644 --- a/wxPython/wx/lib/maskednumctrl.py +++ b/wxPython/wx/lib/maskednumctrl.py @@ -4,7 +4,7 @@ # Created: 09/06/2003 # Copyright: (c) 2003 by Will Sadkin # RCS-ID: $Id$ -# License: wxWindows license +# License: wxWidgets license #---------------------------------------------------------------------------- # NOTE: # This was written to provide a numeric edit control for wxPython that @@ -29,7 +29,7 @@ # are exceeded. # # MaskedNumCtrl is intended to support fixed-point numeric entry, and -# is derived from MaskedTextCtrl. As such, it supports a limited range +# is derived from BaseMaskedTextCtrl. As such, it supports a limited range # of values to comply with a fixed-width entry mask. #---------------------------------------------------------------------------- # 12/09/2003 - Jeff Grimmett (grimmtooth@softhome.net) @@ -65,10 +65,10 @@ Here's the API: MaskedNumCtrl( parent, id = -1, value = 0, - pos = wxDefaultPosition, - size = wxDefaultSize, + pos = wx.DefaultPosition, + size = wx.DefaultSize, style = 0, - validator = wxDefaultValidator, + validator = wx.DefaultValidator, name = "maskednumber", integerWidth = 10, fractionWidth = 0, @@ -87,6 +87,7 @@ Here's the API: emptyBackgroundColour = "White", validBackgroundColour = "White", invalidBackgroundColour = "Yellow", + autoSize = True )

EVT_MASKEDNUM(win, id, func) -
Respond to a wxEVT_COMMAND_MASKED_NUMBER_UPDATED event, generated when +
Respond to a EVT_COMMAND_MASKED_NUMBER_UPDATED event, generated when the value changes. Notice that this event will always be sent when the control's contents changes - whether this is due to user input or comes from the program itself (for example, if SetValue() is called.) @@ -353,6 +363,12 @@ within the control. (The default is True.) the field values on entry.

+
SetAutoSize(bool) +
Resets the autoSize attribute of the control. +
GetAutoSize() +
Returns the current state of the autoSize attribute for the control. +
+
""" @@ -368,8 +384,7 @@ MAXINT = maxint # (constants should be in upper case) MININT = -maxint-1 from wx.tools.dbg import Logger -from wx.lib.maskededit import MaskedEditMixin, MaskedTextCtrl, Field - +from wx.lib.maskededit import MaskedEditMixin, BaseMaskedTextCtrl, Field dbg = Logger() dbg(enable=0) @@ -394,16 +409,55 @@ class MaskedNumNumberUpdatedEvent(wx.PyCommandEvent): #---------------------------------------------------------------------------- +class MaskedNumCtrlAccessorsMixin: + # Define wxMaskedNumCtrl's list of attributes having their own + # Get/Set functions, ignoring those that make no sense for + # an numeric control. + exposed_basectrl_params = ( + 'decimalChar', + 'shiftDecimalChar', + 'groupChar', + 'useParensForNegatives', + 'defaultValue', + 'description', + + 'useFixedWidthFont', + 'autoSize', + 'signedForegroundColour', + 'emptyBackgroundColour', + 'validBackgroundColour', + 'invalidBackgroundColour', + + 'emptyInvalid', + 'validFunc', + 'validRequired', + ) + for param in exposed_basectrl_params: + 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: + # add non-british spellings, for backward-compatibility + 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)) + + + +#---------------------------------------------------------------------------- + +class MaskedNumCtrl(BaseMaskedTextCtrl, MaskedNumCtrlAccessorsMixin): -class MaskedNumCtrl(MaskedTextCtrl): valid_ctrl_params = { 'integerWidth': 10, # by default allow all 32-bit integers - 'fractionWidth': 0, # by default, use integers + 'fractionWidth': 0, # by default, use integers 'decimalChar': '.', # by default, use '.' for decimal point 'allowNegative': True, # by default, allow negative numbers 'useParensForNegatives': False, # by default, use '-' to indicate negatives - 'groupDigits': True, # by default, don't insert grouping + 'groupDigits': True, # by default, don't insert grouping 'groupChar': ',', # by default, use ',' for grouping 'min': None, # by default, no bounds set 'max': None, @@ -415,7 +469,8 @@ class MaskedNumCtrl(MaskedTextCtrl): 'emptyBackgroundColour': "White", 'validBackgroundColour': "White", 'invalidBackgroundColour': "Yellow", - 'useFixedWidthFont': True, # by default, use a fixed-width font + 'useFixedWidthFont': True, # by default, use a fixed-width font + 'autoSize': True, # by default, set the width of the control based on the mask } @@ -488,6 +543,12 @@ class MaskedNumCtrl(MaskedTextCtrl): del init_args['integerWidth'] del init_args['fractionWidth'] + self._autoSize = init_args['autoSize'] + if self._autoSize: + formatcodes = 'FR<' + else: + formatcodes = 'R<' + mask = intmask+fracmask @@ -497,11 +558,11 @@ class MaskedNumCtrl(MaskedTextCtrl): self._typedSign = False # Construct the base control: - MaskedTextCtrl.__init__( + BaseMaskedTextCtrl.__init__( self, parent, id, '', pos, size, style, validator, name, mask = mask, - formatcodes = 'FR<', + formatcodes = formatcodes, fields = fields, validFunc=self.IsInBounds, setupEventHandling = False) @@ -538,7 +599,8 @@ class MaskedNumCtrl(MaskedTextCtrl): if( (kwargs.has_key('integerWidth') and kwargs['integerWidth'] != self._integerWidth) or (kwargs.has_key('fractionWidth') and kwargs['fractionWidth'] != self._fractionWidth) - or (kwargs.has_key('groupDigits') and kwargs['groupDigits'] != self._groupDigits) ): + or (kwargs.has_key('groupDigits') and kwargs['groupDigits'] != self._groupDigits) + or (kwargs.has_key('autoSize') and kwargs['autoSize'] != self._autoSize) ): fields = {} @@ -614,7 +676,7 @@ class MaskedNumCtrl(MaskedTextCtrl): dbg('kwargs:', kwargs) # reprocess existing format codes to ensure proper resulting format: - formatcodes = self.GetFormatcodes() + formatcodes = self.GetCtrlParameter('formatcodes') if kwargs.has_key('allowNegative'): if kwargs['allowNegative'] and '-' not in formatcodes: formatcodes += '-' @@ -641,6 +703,16 @@ class MaskedNumCtrl(MaskedTextCtrl): formatcodes = formatcodes.replace('S','') maskededit_kwargs['formatcodes'] = formatcodes + if kwargs.has_key('autoSize'): + self._autoSize = kwargs['autoSize'] + if kwargs['autoSize'] and 'F' not in formatcodes: + formatcodes += 'F' + maskededit_kwargs['formatcodes'] = formatcodes + elif not kwargs['autoSize'] and 'F' in formatcodes: + formatcodes = formatcodes.replace('F', '') + maskededit_kwargs['formatcodes'] = formatcodes + + if 'r' in formatcodes and self._fractionWidth: # top-level mask should only be right insert if no fractional # part will be shown; ie. if reconfiguring control, remove @@ -648,6 +720,7 @@ class MaskedNumCtrl(MaskedTextCtrl): formatcodes = formatcodes.replace('r', '') maskededit_kwargs['formatcodes'] = formatcodes + if kwargs.has_key('limited'): if kwargs['limited'] and not self._limited: maskededit_kwargs['validRequired'] = True @@ -661,6 +734,7 @@ class MaskedNumCtrl(MaskedTextCtrl): # Record end of integer and place cursor there: integerEnd = self._fields[0]._extent[1] + self.SetInsertionPoint(0) self.SetInsertionPoint(integerEnd) self.SetSelection(integerEnd, integerEnd) @@ -733,7 +807,7 @@ class MaskedNumCtrl(MaskedTextCtrl): dbg('abs(value):', value) self._isNeg = False - elif not self._allowNone and MaskedTextCtrl.GetValue(self) == '': + elif not self._allowNone and BaseMaskedTextCtrl.GetValue(self) == '': if self._min > 0: value = self._min else: @@ -775,7 +849,7 @@ class MaskedNumCtrl(MaskedTextCtrl): else: fracstart, fracend = self._fields[1]._extent if candidate is None: - value = self._toGUI(MaskedTextCtrl.GetValue(self)) + value = self._toGUI(BaseMaskedTextCtrl.GetValue(self)) else: value = self._toGUI(candidate) fracstring = value[fracstart:fracend].strip() @@ -824,8 +898,8 @@ class MaskedNumCtrl(MaskedTextCtrl): if numvalue == "": if self._allowNone: - dbg('calling base MaskedTextCtrl._SetValue(self, "%s")' % value) - MaskedTextCtrl._SetValue(self, value) + dbg('calling base BaseMaskedTextCtrl._SetValue(self, "%s")' % value) + BaseMaskedTextCtrl._SetValue(self, value) self.Refresh() return elif self._min > 0 and self.IsLimited(): @@ -925,7 +999,7 @@ class MaskedNumCtrl(MaskedTextCtrl): # reasonable instead: dbg('setting replacement value:', replacement) self._SetValue(self._toGUI(replacement)) - sel_start = MaskedTextCtrl.GetValue(self).find(str(abs(replacement))) # find where it put the 1, so we can select it + sel_start = BaseMaskedTextCtrl.GetValue(self).find(str(abs(replacement))) # find where it put the 1, so we can select it sel_to = sel_start + len(str(abs(replacement))) dbg('queuing selection of (%d, %d)' %(sel_start, sel_to)) wx.CallAfter(self.SetInsertionPoint, sel_start) @@ -951,8 +1025,8 @@ class MaskedNumCtrl(MaskedTextCtrl): sel_start, sel_to = self._GetSelection() # record current insertion point - dbg('calling base MaskedTextCtrl._SetValue(self, "%s")' % adjvalue) - MaskedTextCtrl._SetValue(self, adjvalue) + dbg('calling BaseMaskedTextCtrl._SetValue(self, "%s")' % adjvalue) + BaseMaskedTextCtrl._SetValue(self, adjvalue) # After all actions so far scheduled, check that resulting cursor # position is appropriate, and move if not: wx.CallAfter(self._CheckInsertionPoint) @@ -985,7 +1059,7 @@ class MaskedNumCtrl(MaskedTextCtrl): # delete next digit to appropriate side: if self._groupDigits: key = event.GetKeyCode() - value = MaskedTextCtrl.GetValue(self) + value = BaseMaskedTextCtrl.GetValue(self) sel_start, sel_to = self._GetSelection() if key == wx.WXK_BACK: @@ -1011,7 +1085,7 @@ class MaskedNumCtrl(MaskedTextCtrl): self.SetInsertionPoint(sel_start) self.SetSelection(sel_start, sel_to+1) - MaskedTextCtrl._OnErase(self, event) + BaseMaskedTextCtrl._OnErase(self, event) dbg(indent=0) @@ -1025,7 +1099,7 @@ class MaskedNumCtrl(MaskedTextCtrl): before passing the events on. """ dbg('MaskedNumCtrl::OnTextChange', indent=1) - if not MaskedTextCtrl._OnTextChange(self, event): + if not BaseMaskedTextCtrl._OnTextChange(self, event): dbg(indent=0) return @@ -1046,7 +1120,7 @@ class MaskedNumCtrl(MaskedTextCtrl): def _GetValue(self): """ - Override of MaskedTextCtrl to allow amixin to get the raw text value of the + Override of BaseMaskedTextCtrl to allow mixin to get the raw text value of the control with this function. """ return wx.TextCtrl.GetValue(self) @@ -1056,7 +1130,7 @@ class MaskedNumCtrl(MaskedTextCtrl): """ Returns the current numeric value of the control. """ - return self._fromGUI( MaskedTextCtrl.GetValue(self) ) + return self._fromGUI( BaseMaskedTextCtrl.GetValue(self) ) def SetValue(self, value): """ @@ -1067,16 +1141,16 @@ class MaskedNumCtrl(MaskedTextCtrl): A ValueError exception will be raised if an invalid value is specified. """ - MaskedTextCtrl.SetValue( self, self._toGUI(value) ) + BaseMaskedTextCtrl.SetValue( self, self._toGUI(value) ) def SetIntegerWidth(self, value): - self.SetCtrlParameters(integerWidth=value) + self.SetParameters(integerWidth=value) def GetIntegerWidth(self): return self._integerWidth def SetFractionWidth(self, value): - self.SetCtrlParameters(fractionWidth=value) + self.SetParameters(fractionWidth=value) def GetFractionWidth(self): return self._fractionWidth @@ -1221,7 +1295,7 @@ class MaskedNumCtrl(MaskedTextCtrl): except ValueError, e: dbg('error getting NumValue(self._toGUI(value)):', e, indent=0) return False - if value == '': + if value.strip() == '': value = None elif self._fractionWidth: value = float(value) @@ -1294,6 +1368,12 @@ class MaskedNumCtrl(MaskedTextCtrl): def GetSelectOnEntry(self): return self._selectOnEntry + def SetAutoSize(self, value): + self.SetParameters(autoSize=value) + def GetAutoSize(self): + return self._autoSize + + # (Other parameter accessors are inherited from base class) @@ -1311,6 +1391,14 @@ class MaskedNumCtrl(MaskedTextCtrl): elif type(value) in (types.StringType, types.UnicodeType): value = self._GetNumValue(value) dbg('cleansed num value: "%s"' % value) + if value == "": + if self.IsNoneAllowed(): + dbg(indent=0) + return self._template + else: + dbg('exception raised:', e, indent=0) + raise ValueError ('wxMaskedNumCtrl requires numeric value, passed %s'% repr(value) ) + # else... try: if self._fractionWidth or value.find('.') != -1: value = float(value) @@ -1380,7 +1468,7 @@ class MaskedNumCtrl(MaskedTextCtrl): # So, to ensure consistency and to prevent spurious ValueErrors, # we make the following test, and react accordingly: # - if value == '': + if value.strip() == '': if not self.IsNoneAllowed(): dbg('empty value; not allowed,returning 0', indent = 0) if self._fractionWidth: @@ -1514,3 +1602,12 @@ i=0 ## =============================## ## 1. Add support for printf-style format specification. ## 2. Add option for repositioning on 'illegal' insertion point. +## +## Version 1.1 +## 1. Fixed .SetIntegerWidth() and .SetFractionWidth() functions. +## 2. Added autoSize parameter, to allow manual sizing of the control. +## 3. Changed inheritance to use wxBaseMaskedTextCtrl, to remove exposure of +## nonsensical parameter methods from the control, so it will work +## properly with Boa. +## 4. Fixed allowNone bug found by user sameerc1@grandecom.net + diff --git a/wxPython/wx/lib/timectrl.py b/wxPython/wx/lib/timectrl.py index c7d08a8ba7..af42f0836b 100644 --- a/wxPython/wx/lib/timectrl.py +++ b/wxPython/wx/lib/timectrl.py @@ -60,12 +60,14 @@ Here's the API for TimeCtrl: TimeCtrl( parent, id = -1, value = '12:00:00 AM', - pos = wxDefaultPosition, - size = wxDefaultSize, + pos = wx.DefaultPosition, + size = wx.DefaultSize, style = wxTE_PROCESS_TAB, - validator = wxDefaultValidator, + validator = wx.DefaultValidator, name = "time", + format = 'HHMMSS', fmt24hr = False, + displaySeconds = True, spinButton = None, min = None, max = None, @@ -80,7 +82,7 @@ Here's the API for TimeCtrl: with SetValue() after instantiation of the control.)
size
The size of the control will be automatically adjusted for 12/24 hour format - if wxDefaultSize is specified. + if wx.DefaultSize is specified.
style
By default, TimeCtrl will process TAB events, by allowing tab to the different cells within the control. @@ -89,11 +91,23 @@ Here's the API for TimeCtrl: of its validation for entry control is handled internally. However, a validator can be supplied to provide data transfer capability to the control.
+
format +
This parameter can be used instead of the fmt24hr and displaySeconds + parameters, respectively; it provides a shorthand way to specify the time + format you want. Accepted values are 'HHMMSS', 'HHMM', '24HHMMSS', and + '24HHMM'. If the format is specified, the other two arguments will be ignored. +
fmt24hr
If True, control will display time in 24 hour time format; if False, it will use 12 hour AM/PM format. SetValue() will adjust values accordingly for the - control, based on the format specified. + control, based on the format specified. (This value is ignored if the format + parameter is specified.)
+
displaySeconds +
If True, control will include a seconds field; if False, it will + just show hours and minutes. (This value is ignored if the format + parameter is specified.) +
spinButton
If specified, this button's events will be bound to the behavior of the TimeCtrl, working like up/down cursor key events. (See BindSpinButton.) @@ -151,7 +165,7 @@ set to Jan 1, 1970.) (Same as GetValue(as_wxDateTime=True); provided for backwar compatibility with previous release.)

-
BindSpinButton(wxSpinBtton) +
BindSpinButton(SpinBtton)
Binds an externally created spin button to the control, so that up/down spin events change the active cell or selection in the control (in addition to the up/down cursor keys.) (This is primarily to allow you to create a "standard" @@ -258,7 +272,7 @@ import types import wx from wx.tools.dbg import Logger -from wx.lib.maskededit import MaskedTextCtrl, Field +from wx.lib.maskededit import BaseMaskedTextCtrl, Field dbg = Logger() dbg(enable=0) @@ -281,11 +295,40 @@ class TimeUpdatedEvent(wx.PyCommandEvent): """Retrieve the value of the time control at the time this event was generated""" return self.value +class TimeCtrlAccessorsMixin: + # Define TimeCtrl's list of attributes having their own + # Get/Set functions, ignoring those that make no sense for + # an numeric control. + exposed_basectrl_params = ( + 'defaultValue', + 'description', -class TimeCtrl(MaskedTextCtrl): + 'useFixedWidthFont', + 'emptyBackgroundColour', + 'validBackgroundColour', + 'invalidBackgroundColour', + + 'validFunc', + 'validRequired', + ) + for param in exposed_basectrl_params: + 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: + # add non-british spellings, for backward-compatibility + 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)) + + +class TimeCtrl(BaseMaskedTextCtrl): valid_ctrl_params = { - 'display_seconds' : True, # by default, shows seconds + 'format' : 'HHMMSS', # default format code + 'displaySeconds' : True, # by default, shows seconds 'min': None, # by default, no bounds set 'max': None, 'limited': False, # by default, no limiting even if bounds set @@ -316,61 +359,39 @@ class TimeCtrl(MaskedTextCtrl): max = self.__max limited = self.__limited self.__posCurrent = 0 + # handle deprecated keword argument name: + if kwargs.has_key('display_seconds'): + kwargs['displaySeconds'] = kwargs['display_seconds'] + del kwargs['display_seconds'] + if not kwargs.has_key('displaySeconds'): + kwargs['displaySeconds'] = True + # (handle positional arg (from original release) differently from rest of kwargs:) + self.__fmt24hr = False + if not kwargs.has_key('format'): + if fmt24hr: + if kwargs.has_key('displaySeconds') and kwargs['displaySeconds']: + kwargs['format'] = '24HHMMSS' + del kwargs['displaySeconds'] + else: + kwargs['format'] = '24HHMM' + else: + if kwargs.has_key('displaySeconds') and kwargs['displaySeconds']: + kwargs['format'] = 'HHMMSS' + del kwargs['displaySeconds'] + else: + kwargs['format'] = 'HHMM' - # (handle positional args (from original release) differently from rest of kwargs:) - self.__fmt24hr = fmt24hr + if not kwargs.has_key('useFixedWidthFont'): + # allow control over font selection: + kwargs['useFixedWidthFont'] = self.__useFixedWidthFont - maskededit_kwargs = {} + maskededit_kwargs = self.SetParameters(**kwargs) - # assign keyword args as appropriate: - for key, param_value in kwargs.items(): - if key not in TimeCtrl.valid_ctrl_params.keys(): - raise AttributeError('invalid keyword argument "%s"' % key) - - if key == "display_seconds": - self.__display_seconds = param_value - - elif key == "min": min = param_value - elif key == "max": max = param_value - elif key == "limited": limited = param_value - - elif key == "useFixedWidthFont": - maskededit_kwargs[key] = param_value - elif key == "oob_color": - maskededit_kwargs['invalidBackgroundColor'] = param_value - - if self.__fmt24hr: - if self.__display_seconds: maskededit_kwargs['autoformat'] = 'MILTIMEHHMMSS' - else: maskededit_kwargs['autoformat'] = 'MILTIMEHHMM' - - # Set hour field to zero-pad, right-insert, require explicit field change, - # select entire field on entry, and require a resultant valid entry - # to allow character entry: - hourfield = Field(formatcodes='0r