# 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
# are exceeded.
#
# MaskedNumCtrl is intended to support fixed-point numeric entry, and
-# is derived from MaskedTextCtrl. As such, it supports a limited range
+# is derived from BaseMaskedTextCtrl. As such, it supports a limited range
# of values to comply with a fixed-width entry mask.
#----------------------------------------------------------------------------
# 12/09/2003 - Jeff Grimmett (grimmtooth@softhome.net)
<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 MaskedEditMixin, MaskedTextCtrl, Field
-
+from wx.lib.maskededit import MaskedEditMixin, BaseMaskedTextCtrl, Field
dbg = Logger()
dbg(enable=0)
#----------------------------------------------------------------------------
+class MaskedNumCtrlAccessorsMixin:
+ # Define wxMaskedNumCtrl's list of attributes having their own
+ # Get/Set functions, ignoring those that make no sense for
+ # an numeric control.
+ exposed_basectrl_params = (
+ 'decimalChar',
+ 'shiftDecimalChar',
+ 'groupChar',
+ 'useParensForNegatives',
+ 'defaultValue',
+ 'description',
+
+ 'useFixedWidthFont',
+ 'autoSize',
+ 'signedForegroundColour',
+ 'emptyBackgroundColour',
+ 'validBackgroundColour',
+ 'invalidBackgroundColour',
+
+ 'emptyInvalid',
+ 'validFunc',
+ 'validRequired',
+ )
+ for param in exposed_basectrl_params:
+ propname = param[0].upper() + param[1:]
+ exec('def Set%s(self, value): self.SetCtrlParameters(%s=value)' % (propname, param))
+ exec('def Get%s(self): return self.GetCtrlParameter("%s")''' % (propname, param))
+
+ if param.find('Colour') != -1:
+ # add non-british spellings, for backward-compatibility
+ propname.replace('Colour', 'Color')
+
+ exec('def Set%s(self, value): self.SetCtrlParameters(%s=value)' % (propname, param))
+ exec('def Get%s(self): return self.GetCtrlParameter("%s")''' % (propname, param))
+
+
+
+#----------------------------------------------------------------------------
+
+class MaskedNumCtrl(BaseMaskedTextCtrl, MaskedNumCtrlAccessorsMixin):
-class MaskedNumCtrl(MaskedTextCtrl):
valid_ctrl_params = {
'integerWidth': 10, # by default allow all 32-bit integers
- 'fractionWidth': 0, # by default, use integers
+ 'fractionWidth': 0, # by default, use integers
'decimalChar': '.', # by default, use '.' for decimal point
'allowNegative': True, # by default, allow negative numbers
'useParensForNegatives': False, # by default, use '-' to indicate negatives
- 'groupDigits': True, # by default, don't insert grouping
+ 'groupDigits': True, # by default, don't insert grouping
'groupChar': ',', # by default, use ',' for grouping
'min': None, # by default, no bounds set
'max': None,
'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
}
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:
- MaskedTextCtrl.__init__(
+ BaseMaskedTextCtrl.__init__(
self, parent, id, '',
pos, size, style, validator, name,
mask = mask,
- formatcodes = 'FR<',
+ formatcodes = formatcodes,
fields = fields,
validFunc=self.IsInBounds,
setupEventHandling = False)
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 = {}
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 MaskedTextCtrl.GetValue(self) == '':
+ elif not self._allowNone and BaseMaskedTextCtrl.GetValue(self) == '':
if self._min > 0:
value = self._min
else:
else:
fracstart, fracend = self._fields[1]._extent
if candidate is None:
- value = self._toGUI(MaskedTextCtrl.GetValue(self))
+ value = self._toGUI(BaseMaskedTextCtrl.GetValue(self))
else:
value = self._toGUI(candidate)
fracstring = value[fracstart:fracend].strip()
if numvalue == "":
if self._allowNone:
- dbg('calling base MaskedTextCtrl._SetValue(self, "%s")' % value)
- MaskedTextCtrl._SetValue(self, value)
+ dbg('calling base BaseMaskedTextCtrl._SetValue(self, "%s")' % value)
+ BaseMaskedTextCtrl._SetValue(self, value)
self.Refresh()
return
elif self._min > 0 and self.IsLimited():
# reasonable instead:
dbg('setting replacement value:', replacement)
self._SetValue(self._toGUI(replacement))
- sel_start = MaskedTextCtrl.GetValue(self).find(str(abs(replacement))) # find where it put the 1, so we can select it
+ sel_start = BaseMaskedTextCtrl.GetValue(self).find(str(abs(replacement))) # find where it put the 1, so we can select it
sel_to = sel_start + len(str(abs(replacement)))
dbg('queuing selection of (%d, %d)' %(sel_start, sel_to))
wx.CallAfter(self.SetInsertionPoint, sel_start)
sel_start, sel_to = self._GetSelection() # record current insertion point
- dbg('calling base MaskedTextCtrl._SetValue(self, "%s")' % adjvalue)
- MaskedTextCtrl._SetValue(self, adjvalue)
+ dbg('calling BaseMaskedTextCtrl._SetValue(self, "%s")' % adjvalue)
+ BaseMaskedTextCtrl._SetValue(self, adjvalue)
# After all actions so far scheduled, check that resulting cursor
# position is appropriate, and move if not:
wx.CallAfter(self._CheckInsertionPoint)
# delete next digit to appropriate side:
if self._groupDigits:
key = event.GetKeyCode()
- value = MaskedTextCtrl.GetValue(self)
+ value = BaseMaskedTextCtrl.GetValue(self)
sel_start, sel_to = self._GetSelection()
if key == wx.WXK_BACK:
self.SetInsertionPoint(sel_start)
self.SetSelection(sel_start, sel_to+1)
- MaskedTextCtrl._OnErase(self, event)
+ BaseMaskedTextCtrl._OnErase(self, event)
dbg(indent=0)
before passing the events on.
"""
dbg('MaskedNumCtrl::OnTextChange', indent=1)
- if not MaskedTextCtrl._OnTextChange(self, event):
+ if not BaseMaskedTextCtrl._OnTextChange(self, event):
dbg(indent=0)
return
def _GetValue(self):
"""
- Override of MaskedTextCtrl to allow amixin to get the raw text value of the
+ Override of BaseMaskedTextCtrl to allow mixin to get the raw text value of the
control with this function.
"""
return wx.TextCtrl.GetValue(self)
"""
Returns the current numeric value of the control.
"""
- return self._fromGUI( MaskedTextCtrl.GetValue(self) )
+ return self._fromGUI( BaseMaskedTextCtrl.GetValue(self) )
def SetValue(self, value):
"""
A ValueError exception will be raised if an invalid value
is specified.
"""
- MaskedTextCtrl.SetValue( self, self._toGUI(value) )
+ BaseMaskedTextCtrl.SetValue( self, self._toGUI(value) )
def SetIntegerWidth(self, value):
- self.SetCtrlParameters(integerWidth=value)
+ self.SetParameters(integerWidth=value)
def GetIntegerWidth(self):
return self._integerWidth
def SetFractionWidth(self, value):
- self.SetCtrlParameters(fractionWidth=value)
+ self.SetParameters(fractionWidth=value)
def GetFractionWidth(self):
return self._fractionWidth
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)
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)
# 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:
## =============================##
## 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
+