MaskedEditCtrl updates from Will Sadkin

git-svn-id: https://svn.wxwidgets.org/svn/wx/wxWidgets/trunk@26096 c3d73ce0-8a6f-49c7-b76d-6d57e0e08775
This commit is contained in:
Robin Dunn
2004-03-05 20:58:16 +00:00
parent 64ff2615ae
commit fffd96b769
4 changed files with 1278 additions and 837 deletions

View File

@@ -4,7 +4,7 @@
# Created: 09/06/2003
# Copyright: (c) 2003 by Will Sadkin
# RCS-ID: $Id$
# License: wxWindows license
# License: wxWidgets license
#----------------------------------------------------------------------------
# NOTE:
# This was written to provide a numeric edit control for wxPython that
@@ -29,7 +29,7 @@
# are exceeded.
#
# MaskedNumCtrl is intended to support fixed-point numeric entry, and
# is derived from MaskedTextCtrl. As such, it supports a limited range
# is derived from BaseMaskedTextCtrl. As such, it supports a limited range
# of values to comply with a fixed-width entry mask.
#----------------------------------------------------------------------------
# 12/09/2003 - Jeff Grimmett (grimmtooth@softhome.net)
@@ -65,10 +65,10 @@ Here's the API:
<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,
@@ -87,6 +87,7 @@ Here's the API:
<B>emptyBackgroundColour</B> = "White",
<B>validBackgroundColour</B> = "White",
<B>invalidBackgroundColour</B> = "Yellow",
<B>autoSize</B> = True
)
</PRE>
<UL>
@@ -177,11 +178,20 @@ Here's the API:
<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.)
@@ -353,6 +363,12 @@ within the control. (The default is True.)
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>
"""
@@ -368,8 +384,7 @@ MAXINT = maxint # (constants should be in upper case)
MININT = -maxint-1
from wx.tools.dbg import Logger
from wx.lib.maskededit import MaskedEditMixin, MaskedTextCtrl, Field
from wx.lib.maskededit import MaskedEditMixin, BaseMaskedTextCtrl, Field
dbg = Logger()
dbg(enable=0)
@@ -394,16 +409,55 @@ class MaskedNumNumberUpdatedEvent(wx.PyCommandEvent):
#----------------------------------------------------------------------------
class MaskedNumCtrlAccessorsMixin:
# Define wxMaskedNumCtrl's list of attributes having their own
# Get/Set functions, ignoring those that make no sense for
# an numeric control.
exposed_basectrl_params = (
'decimalChar',
'shiftDecimalChar',
'groupChar',
'useParensForNegatives',
'defaultValue',
'description',
'useFixedWidthFont',
'autoSize',
'signedForegroundColour',
'emptyBackgroundColour',
'validBackgroundColour',
'invalidBackgroundColour',
'emptyInvalid',
'validFunc',
'validRequired',
)
for param in exposed_basectrl_params:
propname = param[0].upper() + param[1:]
exec('def Set%s(self, value): self.SetCtrlParameters(%s=value)' % (propname, param))
exec('def Get%s(self): return self.GetCtrlParameter("%s")''' % (propname, param))
if param.find('Colour') != -1:
# add non-british spellings, for backward-compatibility
propname.replace('Colour', 'Color')
exec('def Set%s(self, value): self.SetCtrlParameters(%s=value)' % (propname, param))
exec('def Get%s(self): return self.GetCtrlParameter("%s")''' % (propname, param))
#----------------------------------------------------------------------------
class MaskedNumCtrl(BaseMaskedTextCtrl, MaskedNumCtrlAccessorsMixin):
class MaskedNumCtrl(MaskedTextCtrl):
valid_ctrl_params = {
'integerWidth': 10, # by default allow all 32-bit integers
'fractionWidth': 0, # by default, use integers
'fractionWidth': 0, # by default, use integers
'decimalChar': '.', # by default, use '.' for decimal point
'allowNegative': True, # by default, allow negative numbers
'useParensForNegatives': False, # by default, use '-' to indicate negatives
'groupDigits': True, # by default, don't insert grouping
'groupDigits': True, # by default, don't insert grouping
'groupChar': ',', # by default, use ',' for grouping
'min': None, # by default, no bounds set
'max': None,
@@ -415,7 +469,8 @@ class MaskedNumCtrl(MaskedTextCtrl):
'emptyBackgroundColour': "White",
'validBackgroundColour': "White",
'invalidBackgroundColour': "Yellow",
'useFixedWidthFont': True, # by default, use a fixed-width font
'useFixedWidthFont': True, # by default, use a fixed-width font
'autoSize': True, # by default, set the width of the control based on the mask
}
@@ -488,6 +543,12 @@ class MaskedNumCtrl(MaskedTextCtrl):
del init_args['integerWidth']
del init_args['fractionWidth']
self._autoSize = init_args['autoSize']
if self._autoSize:
formatcodes = 'FR<'
else:
formatcodes = 'R<'
mask = intmask+fracmask
@@ -497,11 +558,11 @@ class MaskedNumCtrl(MaskedTextCtrl):
self._typedSign = False
# Construct the base control:
MaskedTextCtrl.__init__(
BaseMaskedTextCtrl.__init__(
self, parent, id, '',
pos, size, style, validator, name,
mask = mask,
formatcodes = 'FR<',
formatcodes = formatcodes,
fields = fields,
validFunc=self.IsInBounds,
setupEventHandling = False)
@@ -538,7 +599,8 @@ class MaskedNumCtrl(MaskedTextCtrl):
if( (kwargs.has_key('integerWidth') and kwargs['integerWidth'] != self._integerWidth)
or (kwargs.has_key('fractionWidth') and kwargs['fractionWidth'] != self._fractionWidth)
or (kwargs.has_key('groupDigits') and kwargs['groupDigits'] != self._groupDigits) ):
or (kwargs.has_key('groupDigits') and kwargs['groupDigits'] != self._groupDigits)
or (kwargs.has_key('autoSize') and kwargs['autoSize'] != self._autoSize) ):
fields = {}
@@ -614,7 +676,7 @@ class MaskedNumCtrl(MaskedTextCtrl):
dbg('kwargs:', kwargs)
# reprocess existing format codes to ensure proper resulting format:
formatcodes = self.GetFormatcodes()
formatcodes = self.GetCtrlParameter('formatcodes')
if kwargs.has_key('allowNegative'):
if kwargs['allowNegative'] and '-' not in formatcodes:
formatcodes += '-'
@@ -641,6 +703,16 @@ class MaskedNumCtrl(MaskedTextCtrl):
formatcodes = formatcodes.replace('S','')
maskededit_kwargs['formatcodes'] = formatcodes
if kwargs.has_key('autoSize'):
self._autoSize = kwargs['autoSize']
if kwargs['autoSize'] and 'F' not in formatcodes:
formatcodes += 'F'
maskededit_kwargs['formatcodes'] = formatcodes
elif not kwargs['autoSize'] and 'F' in formatcodes:
formatcodes = formatcodes.replace('F', '')
maskededit_kwargs['formatcodes'] = formatcodes
if 'r' in formatcodes and self._fractionWidth:
# top-level mask should only be right insert if no fractional
# part will be shown; ie. if reconfiguring control, remove
@@ -648,6 +720,7 @@ class MaskedNumCtrl(MaskedTextCtrl):
formatcodes = formatcodes.replace('r', '')
maskededit_kwargs['formatcodes'] = formatcodes
if kwargs.has_key('limited'):
if kwargs['limited'] and not self._limited:
maskededit_kwargs['validRequired'] = True
@@ -661,6 +734,7 @@ class MaskedNumCtrl(MaskedTextCtrl):
# Record end of integer and place cursor there:
integerEnd = self._fields[0]._extent[1]
self.SetInsertionPoint(0)
self.SetInsertionPoint(integerEnd)
self.SetSelection(integerEnd, integerEnd)
@@ -733,7 +807,7 @@ class MaskedNumCtrl(MaskedTextCtrl):
dbg('abs(value):', value)
self._isNeg = False
elif not self._allowNone and MaskedTextCtrl.GetValue(self) == '':
elif not self._allowNone and BaseMaskedTextCtrl.GetValue(self) == '':
if self._min > 0:
value = self._min
else:
@@ -775,7 +849,7 @@ class MaskedNumCtrl(MaskedTextCtrl):
else:
fracstart, fracend = self._fields[1]._extent
if candidate is None:
value = self._toGUI(MaskedTextCtrl.GetValue(self))
value = self._toGUI(BaseMaskedTextCtrl.GetValue(self))
else:
value = self._toGUI(candidate)
fracstring = value[fracstart:fracend].strip()
@@ -824,8 +898,8 @@ class MaskedNumCtrl(MaskedTextCtrl):
if numvalue == "":
if self._allowNone:
dbg('calling base MaskedTextCtrl._SetValue(self, "%s")' % value)
MaskedTextCtrl._SetValue(self, value)
dbg('calling base BaseMaskedTextCtrl._SetValue(self, "%s")' % value)
BaseMaskedTextCtrl._SetValue(self, value)
self.Refresh()
return
elif self._min > 0 and self.IsLimited():
@@ -925,7 +999,7 @@ class MaskedNumCtrl(MaskedTextCtrl):
# reasonable instead:
dbg('setting replacement value:', replacement)
self._SetValue(self._toGUI(replacement))
sel_start = MaskedTextCtrl.GetValue(self).find(str(abs(replacement))) # find where it put the 1, so we can select it
sel_start = BaseMaskedTextCtrl.GetValue(self).find(str(abs(replacement))) # find where it put the 1, so we can select it
sel_to = sel_start + len(str(abs(replacement)))
dbg('queuing selection of (%d, %d)' %(sel_start, sel_to))
wx.CallAfter(self.SetInsertionPoint, sel_start)
@@ -951,8 +1025,8 @@ class MaskedNumCtrl(MaskedTextCtrl):
sel_start, sel_to = self._GetSelection() # record current insertion point
dbg('calling base MaskedTextCtrl._SetValue(self, "%s")' % adjvalue)
MaskedTextCtrl._SetValue(self, adjvalue)
dbg('calling BaseMaskedTextCtrl._SetValue(self, "%s")' % adjvalue)
BaseMaskedTextCtrl._SetValue(self, adjvalue)
# After all actions so far scheduled, check that resulting cursor
# position is appropriate, and move if not:
wx.CallAfter(self._CheckInsertionPoint)
@@ -985,7 +1059,7 @@ class MaskedNumCtrl(MaskedTextCtrl):
# delete next digit to appropriate side:
if self._groupDigits:
key = event.GetKeyCode()
value = MaskedTextCtrl.GetValue(self)
value = BaseMaskedTextCtrl.GetValue(self)
sel_start, sel_to = self._GetSelection()
if key == wx.WXK_BACK:
@@ -1011,7 +1085,7 @@ class MaskedNumCtrl(MaskedTextCtrl):
self.SetInsertionPoint(sel_start)
self.SetSelection(sel_start, sel_to+1)
MaskedTextCtrl._OnErase(self, event)
BaseMaskedTextCtrl._OnErase(self, event)
dbg(indent=0)
@@ -1025,7 +1099,7 @@ class MaskedNumCtrl(MaskedTextCtrl):
before passing the events on.
"""
dbg('MaskedNumCtrl::OnTextChange', indent=1)
if not MaskedTextCtrl._OnTextChange(self, event):
if not BaseMaskedTextCtrl._OnTextChange(self, event):
dbg(indent=0)
return
@@ -1046,7 +1120,7 @@ class MaskedNumCtrl(MaskedTextCtrl):
def _GetValue(self):
"""
Override of MaskedTextCtrl to allow amixin to get the raw text value of the
Override of BaseMaskedTextCtrl to allow mixin to get the raw text value of the
control with this function.
"""
return wx.TextCtrl.GetValue(self)
@@ -1056,7 +1130,7 @@ class MaskedNumCtrl(MaskedTextCtrl):
"""
Returns the current numeric value of the control.
"""
return self._fromGUI( MaskedTextCtrl.GetValue(self) )
return self._fromGUI( BaseMaskedTextCtrl.GetValue(self) )
def SetValue(self, value):
"""
@@ -1067,16 +1141,16 @@ class MaskedNumCtrl(MaskedTextCtrl):
A ValueError exception will be raised if an invalid value
is specified.
"""
MaskedTextCtrl.SetValue( self, self._toGUI(value) )
BaseMaskedTextCtrl.SetValue( self, self._toGUI(value) )
def SetIntegerWidth(self, value):
self.SetCtrlParameters(integerWidth=value)
self.SetParameters(integerWidth=value)
def GetIntegerWidth(self):
return self._integerWidth
def SetFractionWidth(self, value):
self.SetCtrlParameters(fractionWidth=value)
self.SetParameters(fractionWidth=value)
def GetFractionWidth(self):
return self._fractionWidth
@@ -1221,7 +1295,7 @@ class MaskedNumCtrl(MaskedTextCtrl):
except ValueError, e:
dbg('error getting NumValue(self._toGUI(value)):', e, indent=0)
return False
if value == '':
if value.strip() == '':
value = None
elif self._fractionWidth:
value = float(value)
@@ -1294,6 +1368,12 @@ class MaskedNumCtrl(MaskedTextCtrl):
def GetSelectOnEntry(self):
return self._selectOnEntry
def SetAutoSize(self, value):
self.SetParameters(autoSize=value)
def GetAutoSize(self):
return self._autoSize
# (Other parameter accessors are inherited from base class)
@@ -1311,6 +1391,14 @@ class MaskedNumCtrl(MaskedTextCtrl):
elif type(value) in (types.StringType, types.UnicodeType):
value = self._GetNumValue(value)
dbg('cleansed num value: "%s"' % value)
if value == "":
if self.IsNoneAllowed():
dbg(indent=0)
return self._template
else:
dbg('exception raised:', e, indent=0)
raise ValueError ('wxMaskedNumCtrl requires numeric value, passed %s'% repr(value) )
# else...
try:
if self._fractionWidth or value.find('.') != -1:
value = float(value)
@@ -1380,7 +1468,7 @@ class MaskedNumCtrl(MaskedTextCtrl):
# So, to ensure consistency and to prevent spurious ValueErrors,
# we make the following test, and react accordingly:
#
if value == '':
if value.strip() == '':
if not self.IsNoneAllowed():
dbg('empty value; not allowed,returning 0', indent = 0)
if self._fractionWidth:
@@ -1514,3 +1602,12 @@ i=0
## =============================##
## 1. Add support for printf-style format specification.
## 2. Add option for repositioning on 'illegal' insertion point.
##
## Version 1.1
## 1. Fixed .SetIntegerWidth() and .SetFractionWidth() functions.
## 2. Added autoSize parameter, to allow manual sizing of the control.
## 3. Changed inheritance to use wxBaseMaskedTextCtrl, to remove exposure of
## nonsensical parameter methods from the control, so it will work
## properly with Boa.
## 4. Fixed allowNone bug found by user sameerc1@grandecom.net