X-Git-Url: https://git.saurik.com/wxWidgets.git/blobdiff_plain/c878ceeae8d69f231477ef0f207766093547ab86..e90db86e7e059fc6c6052228238a44b15410782c:/wxPython/wx/lib/masked/numctrl.py?ds=inline diff --git a/wxPython/wx/lib/masked/numctrl.py b/wxPython/wx/lib/masked/numctrl.py index 17c95888d7..73a837039b 100644 --- a/wxPython/wx/lib/masked/numctrl.py +++ b/wxPython/wx/lib/masked/numctrl.py @@ -44,61 +44,61 @@ # o wxMaskedNumCtrl -> masked.NumCtrl # -""" -

-masked.NumCtrl: -

-

-Being derived from MaskedTextCtrl, the control only allows -fixed-point notation. That is, it has a fixed (though reconfigurable) -maximum width for the integer portion and optional fixed width -fractional portion. -

-Here's the API: -

-    masked.NumCtrl(
-         parent, id = -1,
-         value = 0,
-         pos = wx.DefaultPosition,
-         size = wx.DefaultSize,
-         style = 0,
-         validator = wx.DefaultValidator,
-         name = "masked.number",
-         integerWidth = 10,
-         fractionWidth = 0,
-         allowNone = False,
-         allowNegative = True,
-         useParensForNegatives = False,
-         groupDigits = False,
-         groupChar = ',',
-         decimalChar = '.',
-         min = None,
-         max = None,
-         limited = False,
-         selectOnEntry = True,
-         foregroundColour = "Black",
-         signedForegroundColour = "Red",
-         emptyBackgroundColour = "White",
-         validBackgroundColour = "White",
-         invalidBackgroundColour = "Yellow",
-         autoSize = True
-         )
-
- -
-
-
masked.EVT_NUM(win, id, func) -
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.) -
-
-
SetValue(int|long|float|string) -
Sets the value of the control to the value specified, if -possible. The resulting actual value of the control may be -altered to conform to the format of the control, changed -to conform with the bounds set on the control if limited, -or colored if not limited but the value is out-of-bounds. -A ValueError exception will be raised if an invalid value -is specified. -
-
GetValue() -
Retrieves the numeric value from the control. The value -retrieved will be either be returned as a long if the -fractionWidth is 0, or a float otherwise. -
-
-
SetParameters(**kwargs) -
Allows simultaneous setting of various attributes -of the control after construction. Keyword arguments -allowed are the same parameters as supported in the constructor. -
-
-
SetIntegerWidth(value) -
Resets the width of the integer portion of the control. The -value must be >= 1, or an AttributeError exception will result. -This value should account for any grouping characters that might -be inserted (if grouping is enabled), but does not need to account -for the sign, as that is handled separately by the control. -
GetIntegerWidth() -
Returns the current width of the integer portion of the control, -not including any reserved sign position. -
-
-
SetFractionWidth(value) -
Resets the width of the fractional portion of the control. The -value must be >= 0, or an AttributeError exception will result. If -0, the current value of the control will be truncated to an integer -value. -
GetFractionWidth() -
Returns the current width of the fractional portion of the control. -
-
-
SetMin(min=None) -
Resets the minimum value of the control. If a value of None -is provided, then the control will have no explicit minimum value. -If the value specified is greater than the current maximum value, -then the function returns False and the minimum will not change from -its current setting. On success, the function returns True. -
-If successful and the current value is lower than the new lower -bound, if the control is limited, the value will be automatically -adjusted to the new minimum value; if not limited, the value in the -control will be colored as invalid. -
-If min > the max value allowed by the width of the control, -the function will return False, and the min will not be set. -
-
GetMin() -
Gets the current lower bound value for the control. -It will return None if no lower bound is currently specified. -
-
-
SetMax(max=None) -
Resets the maximum value of the control. If a value of None -is provided, then the control will have no explicit maximum value. -If the value specified is less than the current minimum value, then -the function returns False and the maximum will not change from its -current setting. On success, the function returns True. -
-If successful and the current value is greater than the new upper -bound, if the control is limited the value will be automatically -adjusted to this maximum value; if not limited, the value in the -control will be colored as invalid. -
-If max > the max value allowed by the width of the control, -the function will return False, and the max will not be set. -
-
GetMax() -
Gets the current upper bound value for the control. -It will return None if no upper bound is currently specified. -
-
-
SetBounds(min=None,max=None) -
This function is a convenience function for setting the min and max -values at the same time. The function only applies the maximum bound -if setting the minimum bound is successful, and returns True -only if both operations succeed. Note: leaving out an argument -will remove the corresponding bound. -
GetBounds() -
This function returns a two-tuple (min,max), indicating the -current bounds of the control. Each value can be None if -that bound is not set. -
-
-
IsInBounds(value=None) -
Returns True if no value is specified and the current value -of the control falls within the current bounds. This function can also -be called with a value to see if that value would fall within the current -bounds of the given control. -
-
-
SetLimited(bool) -
If called with a value of True, this function will cause the control -to limit the value to fall within the bounds currently specified. -If the control's value currently exceeds the bounds, it will then -be limited accordingly. -If called with a value of False, this function will disable value -limiting, but coloring of out-of-bounds values will still take -place if bounds have been set for the control. -
GetLimited() -
IsLimited() -
Returns True if the control is currently limiting the -value to fall within the current bounds. -
-
-
SetAllowNone(bool) -
If called with a value of True, this function will cause the control -to allow the value to be empty, representing a value of None. -If called with a value of False, this function will prevent the value -from being None. If the value of the control is currently None, -ie. the control is empty, then the value will be changed to that -of the lower bound of the control, or 0 if no lower bound is set. -
GetAllowNone() -
IsNoneAllowed() -
Returns True if the control currently allows its -value to be None. -
-
-
SetAllowNegative(bool) -
If called with a value of True, this function will cause the -control to allow the value to be negative (and reserve space for -displaying the sign. If called with a value of False, and the -value of the control is currently negative, the value of the -control will be converted to the absolute value, and then -limited appropriately based on the existing bounds of the control -(if any). -
GetAllowNegative() -
IsNegativeAllowed() -
Returns True if the control currently permits values -to be negative. -
-
-
SetGroupDigits(bool) -
If called with a value of True, this will make the control -automatically add and manage grouping characters to the presented -value in integer portion of the control. -
GetGroupDigits() -
IsGroupingAllowed() -
Returns True if the control is currently set to group digits. -
-
-
SetGroupChar() -
Sets the grouping character for the integer portion of the -control. (The default grouping character this is ','. -
GetGroupChar() -
Returns the current grouping character for the control. -
-
-
SetSelectOnEntry() -
If called with a value of True, this will make the control -automatically select the contents of each field as it is entered -within the control. (The default is True.) -
GetSelectOnEntry() -
Returns True if the control currently auto selects -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. -
-
-
- + +-------------------------- + +masked.EVT_NUM(win, id, func) + 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.) + + +SetValue(int|long|float|string) + Sets the value of the control to the value specified, if + possible. The resulting actual value of the control may be + altered to conform to the format of the control, changed + to conform with the bounds set on the control if limited, + or colored if not limited but the value is out-of-bounds. + A ValueError exception will be raised if an invalid value + is specified. + +GetValue() + Retrieves the numeric value from the control. The value + retrieved will be either be returned as a long if the + fractionWidth is 0, or a float otherwise. + + +SetParameters(\*\*kwargs) + Allows simultaneous setting of various attributes + of the control after construction. Keyword arguments + allowed are the same parameters as supported in the constructor. + + +SetIntegerWidth(value) + Resets the width of the integer portion of the control. The + value must be >= 1, or an AttributeError exception will result. + This value should account for any grouping characters that might + be inserted (if grouping is enabled), but does not need to account + for the sign, as that is handled separately by the control. +GetIntegerWidth() + Returns the current width of the integer portion of the control, + not including any reserved sign position. + + +SetFractionWidth(value) + Resets the width of the fractional portion of the control. The + value must be >= 0, or an AttributeError exception will result. If + 0, the current value of the control will be truncated to an integer + value. +GetFractionWidth() + Returns the current width of the fractional portion of the control. + + +SetMin(min=None) + Resets the minimum value of the control. If a value of None + is provided, then the control will have no explicit minimum value. + If the value specified is greater than the current maximum value, + then the function returns False and the minimum will not change from + its current setting. On success, the function returns True. + + If successful and the current value is lower than the new lower + bound, if the control is limited, the value will be automatically + adjusted to the new minimum value; if not limited, the value in the + control will be colored as invalid. + + If min > the max value allowed by the width of the control, + the function will return False, and the min will not be set. + +GetMin() + Gets the current lower bound value for the control. + It will return None if no lower bound is currently specified. + + +SetMax(max=None) + Resets the maximum value of the control. If a value of None + is provided, then the control will have no explicit maximum value. + If the value specified is less than the current minimum value, then + the function returns False and the maximum will not change from its + current setting. On success, the function returns True. + + If successful and the current value is greater than the new upper + bound, if the control is limited the value will be automatically + adjusted to this maximum value; if not limited, the value in the + control will be colored as invalid. + + If max > the max value allowed by the width of the control, + the function will return False, and the max will not be set. + +GetMax() + Gets the current upper bound value for the control. + It will return None if no upper bound is currently specified. + + +SetBounds(min=None,max=None) + This function is a convenience function for setting the min and max + values at the same time. The function only applies the maximum bound + if setting the minimum bound is successful, and returns True + only if both operations succeed. Note: leaving out an argument + will remove the corresponding bound. +GetBounds() + This function returns a two-tuple (min,max), indicating the + current bounds of the control. Each value can be None if + that bound is not set. + + +IsInBounds(value=None) + Returns True if no value is specified and the current value + of the control falls within the current bounds. This function can also + be called with a value to see if that value would fall within the current + bounds of the given control. + + +SetLimited(bool) + If called with a value of True, this function will cause the control + to limit the value to fall within the bounds currently specified. + If the control's value currently exceeds the bounds, it will then + be limited accordingly. + If called with a value of False, this function will disable value + limiting, but coloring of out-of-bounds values will still take + place if bounds have been set for the control. + +GetLimited() + +IsLimited() + Returns True if the control is currently limiting the + value to fall within the current bounds. + + +SetAllowNone(bool) + If called with a value of True, this function will cause the control + to allow the value to be empty, representing a value of None. + If called with a value of False, this function will prevent the value + from being None. If the value of the control is currently None, + ie. the control is empty, then the value will be changed to that + of the lower bound of the control, or 0 if no lower bound is set. + +GetAllowNone() + +IsNoneAllowed() + Returns True if the control currently allows its + value to be None. + + +SetAllowNegative(bool) + If called with a value of True, this function will cause the + control to allow the value to be negative (and reserve space for + displaying the sign. If called with a value of False, and the + value of the control is currently negative, the value of the + control will be converted to the absolute value, and then + limited appropriately based on the existing bounds of the control + (if any). + +GetAllowNegative() + +IsNegativeAllowed() + Returns True if the control currently permits values + to be negative. + + +SetGroupDigits(bool) + If called with a value of True, this will make the control + automatically add and manage grouping characters to the presented + value in integer portion of the control. + +GetGroupDigits() + +IsGroupingAllowed() + Returns True if the control is currently set to group digits. + + +SetGroupChar() + Sets the grouping character for the integer portion of the + control. (The default grouping character this is ','. +GetGroupChar() + Returns the current grouping character for the control. + + +SetSelectOnEntry() + If called with a value of True, this will make the control + automatically select the contents of each field as it is entered + within the control. (The default is True.) + GetSelectOnEntry() + Returns True if the control currently auto selects + 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. + """ import copy @@ -386,7 +391,7 @@ MININT = -maxint-1 from wx.tools.dbg import Logger from wx.lib.masked import MaskedEditMixin, Field, BaseMaskedTextCtrl dbg = Logger() -##dbg(enable=0) +##dbg(enable=1) #---------------------------------------------------------------------------- @@ -396,6 +401,10 @@ EVT_NUM = wx.PyEventBinder(wxEVT_COMMAND_MASKED_NUMBER_UPDATED, 1) #---------------------------------------------------------------------------- class NumberUpdatedEvent(wx.PyCommandEvent): + """ + Used to fire an EVT_NUM event whenever the value in a NumCtrl changes. + """ + def __init__(self, id, value = 0, object=None): wx.PyCommandEvent.__init__(self, wxEVT_COMMAND_MASKED_NUMBER_UPDATED, id) @@ -410,9 +419,11 @@ class NumberUpdatedEvent(wx.PyCommandEvent): #---------------------------------------------------------------------------- class NumCtrlAccessorsMixin: - # Define masked.NumCtrl's list of attributes having their own - # Get/Set functions, ignoring those that make no sense for - # an numeric control. + """ + Defines masked.NumCtrl's list of attributes having their own + Get/Set functions, ignoring those that make no sense for + a numeric control. + """ exposed_basectrl_params = ( 'decimalChar', 'shiftDecimalChar', @@ -449,6 +460,11 @@ class NumCtrlAccessorsMixin: #---------------------------------------------------------------------------- class NumCtrl(BaseMaskedTextCtrl, NumCtrlAccessorsMixin): + """ + Masked edit control supporting "native" numeric values, ie. .SetValue(3), for + example, and supporting a variety of formatting options, including automatic + rounding specifiable precision, grouping and decimal place characters, etc. + """ valid_ctrl_params = { @@ -591,7 +607,8 @@ class NumCtrl(BaseMaskedTextCtrl, NumCtrlAccessorsMixin): def SetParameters(self, **kwargs): """ - This routine is used to initialize and reconfigure the control: + This function is used to initialize and reconfigure the control. + See TimeCtrl module overview for available parameters. """ ## dbg('NumCtrl::SetParameters', indent=1) maskededit_kwargs = {} @@ -654,14 +671,27 @@ class NumCtrl(BaseMaskedTextCtrl, NumCtrlAccessorsMixin): maskededit_kwargs['mask'] = intmask+fracmask - if kwargs.has_key('groupChar'): + if kwargs.has_key('groupChar') or kwargs.has_key('decimalChar'): old_groupchar = self._groupChar # save so we can reformat properly -## dbg("old_groupchar: '%s'" % old_groupchar) - maskededit_kwargs['groupChar'] = kwargs['groupChar'] - if kwargs.has_key('decimalChar'): old_decimalchar = self._decimalChar +## dbg("old_groupchar: '%s'" % old_groupchar) ## dbg("old_decimalchar: '%s'" % old_decimalchar) - maskededit_kwargs['decimalChar'] = kwargs['decimalChar'] + groupchar = old_groupchar + decimalchar = old_decimalchar + old_numvalue = self._GetNumValue(self._GetValue()) + + if kwargs.has_key('groupChar'): + maskededit_kwargs['groupChar'] = kwargs['groupChar'] + groupchar = kwargs['groupChar'] + if kwargs.has_key('decimalChar'): + maskededit_kwargs['decimalChar'] = kwargs['decimalChar'] + decimalchar = kwargs['decimalChar'] + + # Add sanity check to make sure these are distinct, and if not, + # raise attribute error + if groupchar == decimalchar: + raise AttributeError('groupChar and decimalChar must be distinct') + # for all other parameters, assign keyword args as appropriate: for key, param_value in kwargs.items(): @@ -733,12 +763,6 @@ class NumCtrl(BaseMaskedTextCtrl, NumCtrlAccessorsMixin): if maskededit_kwargs.keys(): self.SetCtrlParameters(**maskededit_kwargs) - # Record end of integer and place cursor there: - integerEnd = self._fields[0]._extent[1] - self.SetInsertionPoint(0) - self.SetInsertionPoint(integerEnd) - self.SetSelection(integerEnd, integerEnd) - # Go ensure all the format codes necessary are present: orig_intformat = intformat = self.GetFieldParameter(0, 'formatcodes') if 'r' not in intformat: @@ -751,6 +775,17 @@ class NumCtrl(BaseMaskedTextCtrl, NumCtrlAccessorsMixin): else: self.SetCtrlParameters(formatcodes=intformat) + # Record end of integer and place cursor there unless selecting, or select entire field: + integerStart, integerEnd = self._fields[0]._extent + if not self._fields[0]._selectOnFieldEntry: + self.SetInsertionPoint(0) + self.SetInsertionPoint(integerEnd) + self.SetSelection(integerEnd, integerEnd) + else: + self.SetInsertionPoint(0) # include any sign + self.SetSelection(0, integerEnd) + + # Set min and max as appropriate: if kwargs.has_key('min'): min = kwargs['min'] @@ -795,12 +830,20 @@ class NumCtrl(BaseMaskedTextCtrl, NumCtrlAccessorsMixin): # Ensure current value of control obeys any new restrictions imposed: text = self._GetValue() ## dbg('text value: "%s"' % text) - if kwargs.has_key('groupChar') and text.find(old_groupchar) != -1: - text = text.replace(old_groupchar, self._groupChar) - if kwargs.has_key('decimalChar') and text.find(old_decimalchar) != -1: - text = text.replace(old_decimalchar, self._decimalChar) + if kwargs.has_key('groupChar') and self._groupChar != old_groupchar and text.find(old_groupchar) != -1: + text = old_numvalue +## dbg('old_groupchar: "%s" newgroupchar: "%s"' % (old_groupchar, self._groupChar)) + if kwargs.has_key('decimalChar') and self._decimalChar != old_decimalchar and text.find(old_decimalchar) != -1: + text = old_numvalue + if text != self._GetValue(): - wx.TextCtrl.SetValue(self, text) + if self._decimalChar != '.': + # ensure latest decimal char is in "numeric value" so it won't be removed + # when going to the GUI: + text = text.replace('.', self._decimalChar) + newtext = self._toGUI(text) +## dbg('calling wx.TextCtrl.SetValue(self, %s)' % newtext) + wx.TextCtrl.SetValue(self, newtext) value = self.GetValue() @@ -904,6 +947,7 @@ class NumCtrl(BaseMaskedTextCtrl, NumCtrlAccessorsMixin): ## dbg('calling base BaseMaskedTextCtrl._SetValue(self, "%s")' % value) BaseMaskedTextCtrl._SetValue(self, value) self.Refresh() +## dbg(indent=0) return elif self._min > 0 and self.IsLimited(): replacement = self._min @@ -921,6 +965,11 @@ class NumCtrl(BaseMaskedTextCtrl, NumCtrlAccessorsMixin): ## dbg('integer: "%s"' % int) try: + # if a float value, this will implicitly verify against limits, + # and generate an exception if out-of-bounds and limited + # if not a float, it will just return 0.0, and we therefore + # have to test against the limits explicitly after testing + # special cases for handling -0 and empty controls... fracval = self.GetFraction(value) except ValueError, e: ## dbg('Exception:', e, 'must be out of bounds; disallow value') @@ -928,13 +977,20 @@ class NumCtrl(BaseMaskedTextCtrl, NumCtrlAccessorsMixin): ## dbg(indent=0) return - if fracval == 0.0: + if fracval == 0.0: # (can be 0 for floats as well as integers) + # we have to do special testing to account for emptying controls, or -0 + # and/or just leaving the sign character or changing the sign, + # so we can do appropriate things to the value of the control, + # we can't just immediately test to see if the value is valid + # If all of these special cases are not in play, THEN we can do + # a limits check and see if the value is otherwise ok... + ## dbg('self._isNeg?', self._isNeg) if int == '-' and self._oldvalue < 0 and not self._typedSign: ## dbg('just a negative sign; old value < 0; setting replacement of 0') replacement = 0 self._isNeg = False - elif int[:2] == '-0' and self._fractionWidth == 0: + elif int[:2] == '-0': if self._oldvalue < 0: ## dbg('-0; setting replacement of 0') replacement = 0 @@ -949,7 +1005,7 @@ class NumCtrl(BaseMaskedTextCtrl, NumCtrlAccessorsMixin): ## dbg(indent=0) return - elif int == '-' and (self._oldvalue >= 0 or self._typedSign) and self._fractionWidth == 0: + elif int == '-' and (self._oldvalue >= 0 or self._typedSign): if not self._limited or (self._min < -1 and self._max >= -1): ## dbg('just a negative sign; setting replacement of -1') replacement = -1 @@ -987,14 +1043,22 @@ class NumCtrl(BaseMaskedTextCtrl, NumCtrlAccessorsMixin): ## dbg(indent=0) return - if int[0] == '0' and len(int) > 1: -## dbg('numvalue: "%s"' % numvalue.replace(' ', '')) +## dbg('numvalue: "%s"' % numvalue.replace(' ', '')) + # finally, (potentially re) verify that numvalue will pass any limits imposed: + try: if self._fractionWidth: value = self._toGUI(string.atof(numvalue)) else: value = self._toGUI(string.atol(numvalue)) + except ValueError, e: +## dbg('Exception:', e, 'must be out of bounds; disallow value') + self._disallowValue() +## dbg(indent=0) + return + ## dbg('modified value: "%s"' % value) + self._typedSign = False # reset state var if replacement is not None: @@ -1050,18 +1114,20 @@ class NumCtrl(BaseMaskedTextCtrl, NumCtrlAccessorsMixin): ## dbg(indent=0) - def _OnErase( self, event ): + def _OnErase( self, event=None, just_return_value=False ): """ This overrides the base control _OnErase, so that erasing around grouping characters auto selects the digit before or after the grouping character, so that the erasure does the right thing. """ ## dbg('NumCtrl::_OnErase', indent=1) - + if event is None: # called as action routine from Cut() operation. + key = wx.WXK_DELETE + else: + key = event.GetKeyCode() #if grouping digits, make sure deletes next to group char always # delete next digit to appropriate side: if self._groupDigits: - key = event.GetKeyCode() value = BaseMaskedTextCtrl.GetValue(self) sel_start, sel_to = self._GetSelection() @@ -1087,9 +1153,8 @@ class NumCtrl(BaseMaskedTextCtrl, NumCtrlAccessorsMixin): and value[sel_start:sel_to] == self._groupChar ): self.SetInsertionPoint(sel_start) self.SetSelection(sel_start, sel_to+1) - - BaseMaskedTextCtrl._OnErase(self, event) ## dbg(indent=0) + return BaseMaskedTextCtrl._OnErase(self, event, just_return_value) def OnTextChange( self, event ): @@ -1144,7 +1209,9 @@ class NumCtrl(BaseMaskedTextCtrl, NumCtrlAccessorsMixin): A ValueError exception will be raised if an invalid value is specified. """ +## dbg('NumCtrl::SetValue(%s)' % value, indent=1) BaseMaskedTextCtrl.SetValue( self, self._toGUI(value) ) +## dbg(indent=0) def SetIntegerWidth(self, value): @@ -1517,24 +1584,145 @@ class NumCtrl(BaseMaskedTextCtrl, NumCtrlAccessorsMixin): Preprocessor for base control paste; if value needs to be right-justified to fit in control, do so prior to paste: """ -## dbg('NumCtrl::_Paste (value = "%s")' % value) +## dbg('NumCtrl::_Paste (value = "%s")' % value, indent=1) if value is None: paste_text = self._getClipboardContents() else: paste_text = value - - # treat paste as "replace number", if appropriate: sel_start, sel_to = self._GetSelection() - if sel_start == sel_to or self._selectOnEntry and (sel_start, sel_to) == self._fields[0]._extent: - paste_text = self._toGUI(paste_text) - self._SetSelection(0, len(self._mask)) + orig_sel_start = sel_start + orig_sel_to = sel_to +## dbg('selection:', (sel_start, sel_to)) + old_value = self._GetValue() - return MaskedEditMixin._Paste(self, + # + field = self._FindField(sel_start) + edit_start, edit_end = field._extent + + # handle possibility of groupChar being a space: + newtext = paste_text.lstrip() + lspace_count = len(paste_text) - len(newtext) + paste_text = ' ' * lspace_count + newtext.replace(self._groupChar, '').replace('(', '-').replace(')','') + + if field._insertRight and self._groupDigits: + # want to paste to the left; see if it will fit: + left_text = old_value[edit_start:sel_start].lstrip() +## dbg('len(left_text):', len(left_text)) +## dbg('len(paste_text):', len(paste_text)) +## dbg('sel_start - (len(left_text) + len(paste_text)) >= edit_start?', sel_start - (len(left_text) + len(paste_text)) >= edit_start) + if sel_start - (len(left_text) + len(paste_text)) >= edit_start: + # will fit! create effective paste text, and move cursor back to do so: + paste_text = left_text + paste_text + sel_start -= len(paste_text) + sel_start += sel_to - orig_sel_start # decrease by amount selected + else: +## dbg("won't fit left;", 'paste text remains: "%s"' % paste_text) +## dbg('adjusted start before accounting for grouping:', sel_start) +## dbg('adjusted paste_text before accounting for grouping: "%s"' % paste_text) + pass + if self._groupDigits and sel_start != orig_sel_start: + left_len = len(old_value[:sel_to].lstrip()) + # remove group chars from adjusted paste string, and left pad to wipe out + # old characters, so that selection will remove the right chars, and + # readjust will do the right thing: + paste_text = paste_text.replace(self._groupChar,'') + adjcount = left_len - len(paste_text) + paste_text = ' ' * adjcount + paste_text + sel_start = sel_to - len(paste_text) +## dbg('adjusted start after accounting for grouping:', sel_start) +## dbg('adjusted paste_text after accounting for grouping: "%s"' % paste_text) + self.SetInsertionPoint(sel_to) + self.SetSelection(sel_start, sel_to) + + new_text, replace_to = MaskedEditMixin._Paste(self, paste_text, raise_on_invalid=raise_on_invalid, - just_return_value=just_return_value) - - + just_return_value=True) + self._SetInsertionPoint(orig_sel_to) + self._SetSelection(orig_sel_start, orig_sel_to) + if not just_return_value and new_text is not None: + if new_text != self._GetValue(): + self.modified = True + if new_text == '': + self.ClearValue() + else: + wx.CallAfter(self._SetValue, new_text) + wx.CallAfter(self._SetInsertionPoint, replace_to) +## dbg(indent=0) + else: +## dbg(indent=0) + return new_text, replace_to + + def _Undo(self, value=None, prev=None): + '''numctrl's undo is more complicated than the base control's, due to + grouping characters; we don't want to consider them when calculating + the undone portion.''' +## dbg('NumCtrl::_Undo', indent=1) + if value is None: value = self._GetValue() + if prev is None: prev = self._prevValue + if not self._groupDigits: + ignore, (new_sel_start, new_sel_to) = BaseMaskedTextCtrl._Undo(self, value, prev, just_return_results = True) + self._SetValue(prev) + self._SetInsertionPoint(new_sel_start) + self._SetSelection(new_sel_start, new_sel_to) + self._prevSelection = (new_sel_start, new_sel_to) +## dbg('resetting "prev selection" to', self._prevSelection) +## dbg(indent=0) + return + # else... + sel_start, sel_to = self._prevSelection + edit_start, edit_end = self._FindFieldExtent(0) + + adjvalue = self._GetNumValue(value).rjust(self._masklength) + adjprev = self._GetNumValue(prev ).rjust(self._masklength) + + # move selection to account for "ungrouped" value: + left_text = value[sel_start:].lstrip() + numleftgroups = len(left_text) - len(left_text.replace(self._groupChar, '')) + adjsel_start = sel_start + numleftgroups + right_text = value[sel_to:].lstrip() + numrightgroups = len(right_text) - len(right_text.replace(self._groupChar, '')) + adjsel_to = sel_to + numrightgroups +## dbg('adjusting "previous" selection from', (sel_start, sel_to), 'to:', (adjsel_start, adjsel_to)) + self._prevSelection = (adjsel_start, adjsel_to) + + # determine appropriate selection for ungrouped undo + ignore, (new_sel_start, new_sel_to) = BaseMaskedTextCtrl._Undo(self, adjvalue, adjprev, just_return_results = True) + + # adjust new selection based on grouping: + left_len = edit_end - new_sel_start + numleftgroups = left_len / 3 + new_sel_start -= numleftgroups + if numleftgroups and left_len % 3 == 0: + new_sel_start += 1 + + if new_sel_start < self._masklength and prev[new_sel_start] == self._groupChar: + new_sel_start += 1 + + right_len = edit_end - new_sel_to + numrightgroups = right_len / 3 + new_sel_to -= numrightgroups + + if new_sel_to and prev[new_sel_to-1] == self._groupChar: + new_sel_to -= 1 + + if new_sel_start > new_sel_to: + new_sel_to = new_sel_start + + # for numbers, we don't care about leading whitespace; adjust selection if + # it includes leading space. + prev_stripped = prev.lstrip() + prev_start = self._masklength - len(prev_stripped) + if new_sel_start < prev_start: + new_sel_start = prev_start + +## dbg('adjusted selection accounting for grouping:', (new_sel_start, new_sel_to)) + self._SetValue(prev) + self._SetInsertionPoint(new_sel_start) + self._SetSelection(new_sel_start, new_sel_to) + self._prevSelection = (new_sel_start, new_sel_to) +## dbg('resetting "prev selection" to', self._prevSelection) +## dbg(indent=0) #=========================================================================== @@ -1600,12 +1788,19 @@ if __name__ == '__main__': except: traceback.print_exc() -i=0 +__i=0 ## To-Do's: ## =============================## ## 1. Add support for printf-style format specification. ## 2. Add option for repositioning on 'illegal' insertion point. ## +## Version 1.3 +## 1. fixed to allow space for a group char. +## +## Version 1.2 +## 1. Allowed select/replace digits. +## 2. Fixed undo to ignore grouping chars. +## ## Version 1.1 ## 1. Fixed .SetIntegerWidth() and .SetFractionWidth() functions. ## 2. Added autoSize parameter, to allow manual sizing of the control. @@ -1613,4 +1808,4 @@ i=0 ## nonsensical parameter methods from the control, so it will work ## properly with Boa. ## 4. Fixed allowNone bug found by user sameerc1@grandecom.net - +##