# o wxMaskedNumCtrl -> masked.NumCtrl
#
-"""<html><body>
-<P>
-<B>masked.NumCtrl:</B>
-<UL>
-<LI>allows you to get and set integer or floating point numbers as value,</LI>
-<LI>provides bounds support and optional value limiting,</LI>
-<LI>has the right-insert input style that MaskedTextCtrl supports,</LI>
-<LI>provides optional automatic grouping, sign control and format, grouping and decimal
-character selection, etc. etc.</LI>
-</UL>
-<P>
-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.
-<P>
-Here's the API:
-<DL><PRE>
- <B>masked.NumCtrl</B>(
- parent, id = -1,
- <B>value</B> = 0,
- pos = wx.DefaultPosition,
- size = wx.DefaultSize,
- style = 0,
- validator = wx.DefaultValidator,
- name = "masked.number",
- <B>integerWidth</B> = 10,
- <B>fractionWidth</B> = 0,
- <B>allowNone</B> = False,
- <B>allowNegative</B> = True,
- <B>useParensForNegatives</B> = False,
- <B>groupDigits</B> = False,
- <B>groupChar</B> = ',',
- <B>decimalChar</B> = '.',
- <B>min</B> = None,
- <B>max</B> = None,
- <B>limited</B> = False,
- <B>selectOnEntry</b> = True,
- <B>foregroundColour</B> = "Black",
- <B>signedForegroundColour</B> = "Red",
- <B>emptyBackgroundColour</B> = "White",
- <B>validBackgroundColour</B> = "White",
- <B>invalidBackgroundColour</B> = "Yellow",
- <B>autoSize</B> = True
- )
-</PRE>
-<UL>
- <DT><B>value</B>
- <DD>If no initial value is set, the default will be zero, or
+"""
+masked.NumCtrl:
+ - allows you to get and set integer or floating point numbers as value,</LI>
+ - provides bounds support and optional value limiting,</LI>
+ - has the right-insert input style that MaskedTextCtrl supports,</LI>
+ - provides optional automatic grouping, sign control and format, grouping and decimal
+ character selection, etc. etc.</LI>
+
+
+ Being derived from masked.TextCtrl, 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::
+
+ from wx.lib.masked import NumCtrl
+
+ 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
+ )
+
+
+ value
+ If no initial value is set, the default will be zero, or
the minimum value, if specified. If an illegal string is specified,
a ValueError will result. (You can always later set the initial
value with SetValue() after instantiation of the control.)
- <BR>
- <DL><B>integerWidth</B>
- <DD>Indicates how many places to the right of any decimal point
+
+ integerWidth
+ Indicates how many places to the right of any decimal point
should be allowed in the control. This will, perforce, limit
the size of the values that can be entered. This number need
not include space for grouping characters or the sign, if either
mask is automatically by the control. The default of 10
will allow any 32 bit integer value. The minimum value
for integerWidth is 1.
- <BR>
- <DL><B>fractionWidth</B>
- <DD>Indicates how many decimal places to show for numeric value.
+
+ fractionWidth
+ Indicates how many decimal places to show for numeric value.
If default (0), then the control will display and return only
integer or long values.
- <BR>
- <DT><B>allowNone</B>
- <DD>Boolean indicating whether or not the control is allowed to be
+
+ allowNone
+ Boolean indicating whether or not the control is allowed to be
empty, representing a value of None for the control.
- <BR>
- <DT><B>allowNegative</B>
- <DD>Boolean indicating whether or not control is allowed to hold
+
+ allowNegative
+ Boolean indicating whether or not control is allowed to hold
negative numbers.
- <BR>
- <DT><B>useParensForNegatives</B>
- <DD>If true, this will cause negative numbers to be displayed with ()s
+
+ useParensForNegatives
+ If true, this will cause negative numbers to be displayed with ()s
rather than -, (although '-' will still trigger a negative number.)
- <BR>
- <DT><B>groupDigits</B>
- <DD>Indicates whether or not grouping characters should be allowed and/or
+
+ groupDigits
+ Indicates whether or not grouping characters should be allowed and/or
inserted when leaving the control or the decimal character is entered.
- <BR>
- <DT><B>groupChar</B>
- <DD>What grouping character will be used if allowed. (By default ',')
- <BR>
- <DT><B>decimalChar</B>
- <DD>If fractionWidth is > 0, what character will be used to represent
+
+ groupChar
+ What grouping character will be used if allowed. (By default ',')
+
+ decimalChar
+ If fractionWidth is > 0, what character will be used to represent
the decimal point. (By default '.')
- <BR>
- <DL><B>min</B>
- <DD>The minimum value that the control should allow. This can be also be
+
+ min
+ The minimum value that the control should allow. This can be also be
adjusted with SetMin(). If the control is not limited, any value
below this bound will result in a background colored with the current
invalidBackgroundColour. If the min specified will not fit into the
control, the min setting will be ignored.
- <BR>
- <DT><B>max</B>
- <DD>The maximum value that the control should allow. This can be
+
+ max
+ The maximum value that the control should allow. This can be
adjusted with SetMax(). If the control is not limited, any value
above this bound will result in a background colored with the current
invalidBackgroundColour. If the max specified will not fit into the
control, the max setting will be ignored.
- <BR>
- <DT><B>limited</B>
- <DD>Boolean indicating whether the control prevents values from
+
+ limited
+ Boolean indicating whether the control prevents values from
exceeding the currently set minimum and maximum values (bounds).
If False and bounds are set, out-of-bounds values will
result in a background colored with the current invalidBackgroundColour.
- <BR>
- <DT><B>selectOnEntry</B>
- <DD>Boolean indicating whether or not the value in each field of the
+
+ selectOnEntry
+ Boolean indicating whether or not the value in each field of the
control should be automatically selected (for replacement) when
that field is entered, either by cursor movement or tabbing.
This can be desirable when using these controls for rapid data entry.
- <BR>
- <DT><B>foregroundColour</B>
- <DD>Color value used for positive values of the control.
- <BR>
- <DT><B>signedForegroundColour</B>
- <DD>Color value used for negative values of the control.
- <BR>
- <DT><B>emptyBackgroundColour</B>
- <DD>What background color to use when the control is considered
+
+ foregroundColour
+ Color value used for positive values of the control.
+
+ signedForegroundColour
+ Color value used for negative values of the control.
+
+ emptyBackgroundColour
+ What background color to use when the control is considered
"empty." (allow_none must be set to trigger this behavior.)
- <BR>
- <DT><B>validBackgroundColour</B>
- <DD>What background color to use when the control value is
+
+ validBackgroundColour
+ What background color to use when the control value is
considered valid.
- <BR>
- <DT><B>invalidBackgroundColour</B>
- <DD>Color value used for illegal values or values out-of-bounds of the
+
+ invalidBackgroundColour
+ Color value used for illegal values or values out-of-bounds of the
control when the bounds are set but the control is not limited.
- <BR>
- <DT><B>autoSize</B>
- <DD>Boolean indicating whether or not the control should set its own
+
+ autoSize
+ Boolean indicating whether or not the control should set its own
width based on the integer and fraction widths. True by default.
- <B><I>Note:</I></B> Setting this to False will produce seemingly odd
+ <I>Note:</I> Setting this to False will produce seemingly odd
behavior unless the control is large enough to hold the maximum
specified value given the widths and the sign positions; if not,
the control will appear to "jump around" as the contents scroll.
(ie. autoSize is highly recommended.)
-</UL>
-<BR>
-<BR>
-<DT><B>masked.EVT_NUM(win, id, func)</B>
-<DD>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.)
-<BR>
-<BR>
-<DT><B>SetValue(int|long|float|string)</B>
-<DD>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.
-<BR>
-<DT><B>GetValue()</B>
-<DD>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.
-<BR>
-<BR>
-<DT><B>SetParameters(**kwargs)</B>
-<DD>Allows simultaneous setting of various attributes
-of the control after construction. Keyword arguments
-allowed are the same parameters as supported in the constructor.
-<BR>
-<BR>
-<DT><B>SetIntegerWidth(value)</B>
-<DD>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.
-<DT><B>GetIntegerWidth()</B>
-<DD>Returns the current width of the integer portion of the control,
-not including any reserved sign position.
-<BR>
-<BR>
-<DT><B>SetFractionWidth(value)</B>
-<DD>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.
-<DT><B>GetFractionWidth()</B>
-<DD>Returns the current width of the fractional portion of the control.
-<BR>
-<BR>
-<DT><B>SetMin(min=None)</B>
-<DD>Resets the minimum value of the control. If a value of <I>None</I>
-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.
-<DT><DD>
-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.
-<DT><DD>
-If min > the max value allowed by the width of the control,
-the function will return False, and the min will not be set.
-<BR>
-<DT><B>GetMin()</B>
-<DD>Gets the current lower bound value for the control.
-It will return None if no lower bound is currently specified.
-<BR>
-<BR>
-<DT><B>SetMax(max=None)</B>
-<DD>Resets the maximum value of the control. If a value of <I>None</I>
-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.
-<DT><DD>
-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.
-<DT><DD>
-If max > the max value allowed by the width of the control,
-the function will return False, and the max will not be set.
-<BR>
-<DT><B>GetMax()</B>
-<DD>Gets the current upper bound value for the control.
-It will return None if no upper bound is currently specified.
-<BR>
-<BR>
-<DT><B>SetBounds(min=None,max=None)</B>
-<DD>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. <B><I>Note:</I></B> leaving out an argument
-will remove the corresponding bound.
-<DT><B>GetBounds()</B>
-<DD>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.
-<BR>
-<BR>
-<DT><B>IsInBounds(value=None)</B>
-<DD>Returns <I>True</I> 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.
-<BR>
-<BR>
-<DT><B>SetLimited(bool)</B>
-<DD>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.
-<DT><B>GetLimited()</B>
-<DT><B>IsLimited()</B>
-<DD>Returns <I>True</I> if the control is currently limiting the
-value to fall within the current bounds.
-<BR>
-<BR>
-<DT><B>SetAllowNone(bool)</B>
-<DD>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.
-<DT><B>GetAllowNone()</B>
-<DT><B>IsNoneAllowed()</B>
-<DD>Returns <I>True</I> if the control currently allows its
-value to be None.
-<BR>
-<BR>
-<DT><B>SetAllowNegative(bool)</B>
-<DD>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).
-<DT><B>GetAllowNegative()</B>
-<DT><B>IsNegativeAllowed()</B>
-<DD>Returns <I>True</I> if the control currently permits values
-to be negative.
-<BR>
-<BR>
-<DT><B>SetGroupDigits(bool)</B>
-<DD>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.
-<DT><B>GetGroupDigits()</B>
-<DT><B>IsGroupingAllowed()</B>
-<DD>Returns <I>True</I> if the control is currently set to group digits.
-<BR>
-<BR>
-<DT><B>SetGroupChar()</B>
-<DD>Sets the grouping character for the integer portion of the
-control. (The default grouping character this is ','.
-<DT><B>GetGroupChar()</B>
-<DD>Returns the current grouping character for the control.
-<BR>
-<BR>
-<DT><B>SetSelectOnEntry()</B>
-<DD>If called with a value of <I>True</I>, this will make the control
-automatically select the contents of each field as it is entered
-within the control. (The default is True.)
-<DT><B>GetSelectOnEntry()</B>
-<DD>Returns <I>True</I> if the control currently auto selects
-the field values on entry.
-<BR>
-<BR>
-<DT><B>SetAutoSize(bool)</B>
-<DD>Resets the autoSize attribute of the control.
-<DT><B>GetAutoSize()</B>
-<DD>Returns the current state of the autoSize attribute for the control.
-<BR>
-<BR>
-</DL>
-</body></html>
+
+--------------------------
+
+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 <I>None</I>
+ 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 <I>None</I>
+ 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. <B><I>Note:</I> 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 <I>True</I> 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 <I>True</I> 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 <I>True</I> 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 <I>True</I> 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 <I>True</I> 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 <I>True</I>, 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 <I>True</I> 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
from wx.tools.dbg import Logger
from wx.lib.masked import MaskedEditMixin, Field, BaseMaskedTextCtrl
dbg = Logger()
-##dbg(enable=0)
+##dbg(enable=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)
#----------------------------------------------------------------------------
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',
#----------------------------------------------------------------------------
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 = {
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 = {}
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():
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:
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']
# 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()
## 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
## 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')
## 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
## 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
## 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:
## 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()
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 ):
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):
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)
#===========================================================================
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.
## nonsensical parameter methods from the control, so it will work
## properly with Boa.
## 4. Fixed allowNone bug found by user sameerc1@grandecom.net
-
+##