# 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
# does things like right-insert (like a calculator), and does grouping, etc.
-# (ie. the features of wxMaskedTextCtrl), but allows Get/Set of numeric
+# (ie. the features of MaskedTextCtrl), but allows Get/Set of numeric
# values, rather than text.
#
-# wxMaskedNumCtrl permits integer, and floating point values to be set
+# MaskedNumCtrl permits integer, and floating point values to be set
# retrieved or set via .GetValue() and .SetValue() (type chosen based on
# fraction width, and provides an EVT_MASKEDNUM() event function for trapping
# changes to the control.
# Similarly, replacing the contents of the control with '-' will result in
# a selected (absolute) value of -1.
#
-# wxMaskedNumCtrl also supports range limits, with the option of either
+# MaskedNumCtrl also supports range limits, with the option of either
# enforcing them or simply coloring the text of the control if the limits
# are exceeded.
#
-# wxMaskedNumCtrl is intended to support fixed-point numeric entry, and
-# is derived from wxMaskedTextCtrl. As such, it supports a limited range
+# MaskedNumCtrl is intended to support fixed-point numeric entry, and
+# 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)
#
# o Updated for wx namespace
#
+# 12/20/2003 - Jeff Grimmett (grimmtooth@softhome.net)
+#
+# o wxMaskedEditMixin -> MaskedEditMixin
+# o wxMaskedTextCtrl -> MaskedTextCtrl
+# o wxMaskedNumNumberUpdatedEvent -> MaskedNumNumberUpdatedEvent
+# o wxMaskedNumCtrl -> MaskedNumCtrl
+#
"""<html><body>
<P>
-<B>wxMaskedNumCtrl:</B>
+<B>MaskedNumCtrl:</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 wxMaskedTextCtrl supports,</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 wxMaskedTextCtrl, the control only allows
+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>wxMaskedNumCtrl</B>(
+ <B>MaskedNumCtrl</B>(
parent, id = -1,
<B>value</B> = 0,
- pos = wxDefaultPosition,
- size = wxDefaultSize,
+ pos = wx.DefaultPosition,
+ size = wx.DefaultSize,
style = 0,
- validator = wxDefaultValidator,
+ validator = wx.DefaultValidator,
name = "maskednumber",
<B>integerWidth</B> = 10,
<B>fractionWidth</B> = 0,
<B>emptyBackgroundColour</B> = "White",
<B>validBackgroundColour</B> = "White",
<B>invalidBackgroundColour</B> = "Yellow",
+ <B>autoSize</B> = True
)
</PRE>
<UL>
<DT><B>invalidBackgroundColour</B>
<DD>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
+ width based on the integer and fraction widths. True by default.
+ <B><I>Note:</I></B> 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>EVT_MASKEDNUM(win, id, func)</B>
-<DD>Respond to a wxEVT_COMMAND_MASKED_NUMBER_UPDATED event, generated when
+<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.)
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>
"""
MININT = -maxint-1
from wx.tools.dbg import Logger
-from wx.lib.maskededit import wxMaskedEditMixin, wxMaskedTextCtrl, Field
-
+from wx.lib.maskededit import MaskedEditMixin, BaseMaskedTextCtrl, Field
dbg = Logger()
dbg(enable=0)
#----------------------------------------------------------------------------
-class wxMaskedNumNumberUpdatedEvent(wx.PyCommandEvent):
+class MaskedNumNumberUpdatedEvent(wx.PyCommandEvent):
def __init__(self, id, value = 0, object=None):
wx.PyCommandEvent.__init__(self, wxEVT_COMMAND_MASKED_NUMBER_UPDATED, id)
#----------------------------------------------------------------------------
+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 wxMaskedNumCtrl(wxMaskedTextCtrl):
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,
'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
}
name = "maskednum",
**kwargs ):
- dbg('wxMaskedNumCtrl::__init__', indent=1)
+ dbg('MaskedNumCtrl::__init__', indent=1)
# Set defaults for control:
dbg('setting defaults:')
- for key, param_value in wxMaskedNumCtrl.valid_ctrl_params.items():
+ for key, param_value in MaskedNumCtrl.valid_ctrl_params.items():
# This is done this way to make setattr behave consistently with
# "private attribute" name mangling
setattr(self, '_' + key, copy.copy(param_value))
# Assign defaults for all attributes:
- init_args = copy.deepcopy(wxMaskedNumCtrl.valid_ctrl_params)
+ init_args = copy.deepcopy(MaskedNumCtrl.valid_ctrl_params)
dbg('kwargs:', kwargs)
for key, param_value in kwargs.items():
key = key.replace('Color', 'Colour')
- if key not in wxMaskedNumCtrl.valid_ctrl_params.keys():
+ if key not in MaskedNumCtrl.valid_ctrl_params.keys():
raise AttributeError('invalid keyword argument "%s"' % key)
else:
init_args[key] = param_value
del init_args['integerWidth']
del init_args['fractionWidth']
+ self._autoSize = init_args['autoSize']
+ if self._autoSize:
+ formatcodes = 'FR<'
+ else:
+ formatcodes = 'R<'
+
mask = intmask+fracmask
self._typedSign = False
# Construct the base control:
- wxMaskedTextCtrl.__init__(
+ BaseMaskedTextCtrl.__init__(
self, parent, id, '',
pos, size, style, validator, name,
mask = mask,
- formatcodes = 'FR<',
+ formatcodes = formatcodes,
fields = fields,
validFunc=self.IsInBounds,
setupEventHandling = False)
# Ensure proper coloring:
self.Refresh()
- dbg('finished wxMaskedNumCtrl::__init__', indent=0)
+ dbg('finished MaskedNumCtrl::__init__', indent=0)
def SetParameters(self, **kwargs):
"""
This routine is used to initialize and reconfigure the control:
"""
- dbg('wxMaskedNumCtrl::SetParameters', indent=1)
+ dbg('MaskedNumCtrl::SetParameters', indent=1)
maskededit_kwargs = {}
reset_fraction_width = False
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 = {}
# for all other parameters, assign keyword args as appropriate:
for key, param_value in kwargs.items():
key = key.replace('Color', 'Colour')
- if key not in wxMaskedNumCtrl.valid_ctrl_params.keys():
+ if key not in MaskedNumCtrl.valid_ctrl_params.keys():
raise AttributeError('invalid keyword argument "%s"' % key)
- elif key not in wxMaskedEditMixin.valid_ctrl_params.keys():
+ elif key not in MaskedEditMixin.valid_ctrl_params.keys():
setattr(self, '_' + key, param_value)
elif key in ('mask', 'autoformat'): # disallow explicit setting of mask
raise AttributeError('invalid keyword argument "%s"' % key)
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 += '-'
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
formatcodes = formatcodes.replace('r', '')
maskededit_kwargs['formatcodes'] = formatcodes
+
if kwargs.has_key('limited'):
if kwargs['limited'] and not self._limited:
maskededit_kwargs['validRequired'] = True
# Record end of integer and place cursor there:
integerEnd = self._fields[0]._extent[1]
+ self.SetInsertionPoint(0)
self.SetInsertionPoint(integerEnd)
self.SetSelection(integerEnd, integerEnd)
dbg('abs(value):', value)
self._isNeg = False
- elif not self._allowNone and wxMaskedTextCtrl.GetValue(self) == '':
+ elif not self._allowNone and BaseMaskedTextCtrl.GetValue(self) == '':
if self._min > 0:
value = self._min
else:
sel_start, sel_to = self.GetSelection()
self._SetValue(self._toGUI(value))
self.Refresh() # recolor as appropriate
- dbg('finished wxMaskedNumCtrl::SetParameters', indent=0)
+ dbg('finished MaskedNumCtrl::SetParameters', indent=0)
else:
fracstart, fracend = self._fields[1]._extent
if candidate is None:
- value = self._toGUI(wxMaskedTextCtrl.GetValue(self))
+ value = self._toGUI(BaseMaskedTextCtrl.GetValue(self))
else:
value = self._toGUI(candidate)
fracstring = value[fracstart:fracend].strip()
return string.atof(fracstring)
def _OnChangeSign(self, event):
- dbg('wxMaskedNumCtrl::_OnChangeSign', indent=1)
+ dbg('MaskedNumCtrl::_OnChangeSign', indent=1)
self._typedSign = True
- wxMaskedEditMixin._OnChangeSign(self, event)
+ MaskedEditMixin._OnChangeSign(self, event)
dbg(indent=0)
def _disallowValue(self):
- dbg('wxMaskedNumCtrl::_disallowValue')
+ dbg('MaskedNumCtrl::_disallowValue')
# limited and -1 is out of bounds
if self._typedSign:
self._isNeg = False
by the user.
"""
- dbg('wxMaskedNumCtrl::_SetValue("%s")' % value, indent=1)
+ dbg('MaskedNumCtrl::_SetValue("%s")' % value, indent=1)
if( (self._fractionWidth and value.find(self._decimalChar) == -1) or
(self._fractionWidth == 0 and value.find(self._decimalChar) != -1) ) :
if numvalue == "":
if self._allowNone:
- dbg('calling base wxMaskedTextCtrl._SetValue(self, "%s")' % value)
- wxMaskedTextCtrl._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():
# reasonable instead:
dbg('setting replacement value:', replacement)
self._SetValue(self._toGUI(replacement))
- sel_start = wxMaskedTextCtrl.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)
sel_start, sel_to = self._GetSelection() # record current insertion point
- dbg('calling base wxMaskedTextCtrl._SetValue(self, "%s")' % adjvalue)
- wxMaskedTextCtrl._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)
- dbg('finished wxMaskedNumCtrl::_SetValue', indent=0)
+ dbg('finished MaskedNumCtrl::_SetValue', indent=0)
def _CheckInsertionPoint(self):
# If current insertion point is before the end of the integer and
# its before the 1st digit, place it just after the sign position:
- dbg('wxMaskedNumCtrl::CheckInsertionPoint', indent=1)
+ dbg('MaskedNumCtrl::CheckInsertionPoint', indent=1)
sel_start, sel_to = self._GetSelection()
text = self._GetValue()
if sel_to < self._fields[0]._extent[1] and text[sel_to] in (' ', '-', '('):
grouping characters auto selects the digit before or after the
grouping character, so that the erasure does the right thing.
"""
- dbg('wxMaskedNumCtrl::_OnErase', indent=1)
+ dbg('MaskedNumCtrl::_OnErase', indent=1)
#if grouping digits, make sure deletes next to group char always
# delete next digit to appropriate side:
if self._groupDigits:
key = event.GetKeyCode()
- value = wxMaskedTextCtrl.GetValue(self)
+ value = BaseMaskedTextCtrl.GetValue(self)
sel_start, sel_to = self._GetSelection()
if key == wx.WXK_BACK:
self.SetInsertionPoint(sel_start)
self.SetSelection(sel_start, sel_to+1)
- wxMaskedTextCtrl._OnErase(self, event)
+ BaseMaskedTextCtrl._OnErase(self, event)
dbg(indent=0)
text events. So we check for actual changes to the text
before passing the events on.
"""
- dbg('wxMaskedNumCtrl::OnTextChange', indent=1)
- if not wxMaskedTextCtrl._OnTextChange(self, event):
+ dbg('MaskedNumCtrl::OnTextChange', indent=1)
+ if not BaseMaskedTextCtrl._OnTextChange(self, event):
dbg(indent=0)
return
if value != self._oldvalue:
try:
self.GetEventHandler().ProcessEvent(
- wxMaskedNumNumberUpdatedEvent( self.GetId(), self.GetValue(), self ) )
+ MaskedNumNumberUpdatedEvent( self.GetId(), self.GetValue(), self ) )
except ValueError:
dbg(indent=0)
return
def _GetValue(self):
"""
- Override of wxMaskedTextCtrl 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)
"""
Returns the current numeric value of the control.
"""
- return self._fromGUI( wxMaskedTextCtrl.GetValue(self) )
+ return self._fromGUI( BaseMaskedTextCtrl.GetValue(self) )
def SetValue(self, value):
"""
A ValueError exception will be raised if an invalid value
is specified.
"""
- wxMaskedTextCtrl.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
If min > the max value allowed by the width of the control,
the function will return False, and the min will not be set.
"""
- dbg('wxMaskedNumCtrl::SetMin(%s)' % repr(min), indent=1)
+ dbg('MaskedNumCtrl::SetMin(%s)' % repr(min), indent=1)
if( self._max is None
or min is None
or (self._max is not None and self._max >= min) ):
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)
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)
type and bounds checking and raises ValueError if argument is
not a valid value.
"""
- dbg('wxMaskedNumCtrl::_toGUI(%s)' % repr(value), indent=1)
+ dbg('MaskedNumCtrl::_toGUI(%s)' % repr(value), indent=1)
if value is None and self.IsNoneAllowed():
dbg(indent=0)
return self._template
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)
value = long(value)
except Exception, e:
dbg('exception raised:', e, indent=0)
- raise ValueError ('wxMaskedNumCtrl requires numeric value, passed %s'% repr(value) )
+ raise ValueError ('MaskedNumCtrl requires numeric value, passed %s'% repr(value) )
elif type(value) not in (types.IntType, types.LongType, types.FloatType):
dbg(indent=0)
raise ValueError (
- 'wxMaskedNumCtrl requires numeric value, passed %s'% repr(value) )
+ 'MaskedNumCtrl requires numeric value, passed %s'% repr(value) )
if not self._allowNegative and value < 0:
raise ValueError (
Conversion function used in getting the value of the control.
"""
dbg(suspend=0)
- dbg('wxMaskedNumCtrl::_fromGUI(%s)' % value, indent=1)
+ dbg('MaskedNumCtrl::_fromGUI(%s)' % value, indent=1)
# One or more of the underlying text control implementations
# issue an intermediate EVT_TEXT when replacing the control's
# value, where the intermediate value is an empty string.
# 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:
Preprocessor for base control paste; if value needs to be right-justified
to fit in control, do so prior to paste:
"""
- dbg('wxMaskedNumCtrl::_Paste (value = "%s")' % value)
+ dbg('MaskedNumCtrl::_Paste (value = "%s")' % value)
if value is None:
paste_text = self._getClipboardContents()
else:
paste_text = self._toGUI(paste_text)
self._SetSelection(0, len(self._mask))
- return wxMaskedEditMixin._Paste(self,
+ return MaskedEditMixin._Paste(self,
paste_text,
raise_on_invalid=raise_on_invalid,
just_return_value=just_return_value)
style = wx.DEFAULT_DIALOG_STYLE ):
wx.Dialog.__init__(self, parent, id, title, pos, size, style)
- self.int_ctrl = wxMaskedNumCtrl(self, wx.NewId(), size=(55,20))
+ self.int_ctrl = MaskedNumCtrl(self, wx.NewId(), size=(55,20))
self.OK = wx.Button( self, wx.ID_OK, "OK")
self.Cancel = wx.Button( self, wx.ID_CANCEL, "Cancel")
return True
def OnClick(self, event):
- dlg = myDialog(self.panel, -1, "test wxMaskedNumCtrl")
+ dlg = myDialog(self.panel, -1, "test MaskedNumCtrl")
dlg.int_ctrl.SetValue(501)
dlg.int_ctrl.SetInsertionPoint(1)
dlg.int_ctrl.SetSelection(1,2)
## =============================##
## 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
+