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,6 +4,7 @@ import sys
import traceback
import wx
import wx.lib.maskededit as me
import wx.lib.maskednumctrl as mnum
#----------------------------------------------------------------------
@ -31,13 +32,13 @@ The controls at the top reconfigure the resulting control at the bottom.
)
groupcharlabel = wx.StaticText( panel,-1, "Grouping char:" )
self.groupchar = mnum.MaskedTextCtrl(
self.groupchar = me.MaskedTextCtrl(
panel, -1, value=',', mask='&', excludeChars = '-()',
formatcodes='F', emptyInvalid=True, validRequired=True
)
decimalcharlabel = wx.StaticText( panel,-1, "Decimal char:" )
self.decimalchar = mnum.MaskedTextCtrl(
self.decimalchar = me.MaskedTextCtrl(
panel, -1, value='.', mask='&', excludeChars = '-()',
formatcodes='F', emptyInvalid=True, validRequired=True
)

File diff suppressed because it is too large Load Diff

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,8 +409,47 @@ 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
@ -416,6 +470,7 @@ class MaskedNumCtrl(MaskedTextCtrl):
'validBackgroundColour': "White",
'invalidBackgroundColour': "Yellow",
'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

View File

@ -60,12 +60,14 @@ Here's the API for TimeCtrl:
<B>TimeCtrl</B>(
parent, id = -1,
<B>value</B> = '12:00:00 AM',
pos = wxDefaultPosition,
size = wxDefaultSize,
pos = wx.DefaultPosition,
size = wx.DefaultSize,
<B>style</B> = wxTE_PROCESS_TAB,
<B>validator</B> = wxDefaultValidator,
<B>validator</B> = wx.DefaultValidator,
name = "time",
<B>format</B> = 'HHMMSS',
<B>fmt24hr</B> = False,
<B>displaySeconds</B> = True,
<B>spinButton</B> = None,
<B>min</B> = None,
<B>max</B> = None,
@ -80,7 +82,7 @@ Here's the API for TimeCtrl:
with SetValue() after instantiation of the control.)
<DL><B>size</B>
<DD>The size of the control will be automatically adjusted for 12/24 hour format
if wxDefaultSize is specified.
if wx.DefaultSize is specified.
<DT><B>style</B>
<DD>By default, TimeCtrl will process TAB events, by allowing tab to the
different cells within the control.
@ -89,10 +91,22 @@ Here's the API for TimeCtrl:
of its validation for entry control is handled internally. However, a validator
can be supplied to provide data transfer capability to the control.
<BR>
<DT><B>format</B>
<DD>This parameter can be used instead of the fmt24hr and displaySeconds
parameters, respectively; it provides a shorthand way to specify the time
format you want. Accepted values are 'HHMMSS', 'HHMM', '24HHMMSS', and
'24HHMM'. If the format is specified, the other two arguments will be ignored.
<BR>
<DT><B>fmt24hr</B>
<DD>If True, control will display time in 24 hour time format; if False, it will
use 12 hour AM/PM format. SetValue() will adjust values accordingly for the
control, based on the format specified.
control, based on the format specified. (This value is ignored if the <i>format</i>
parameter is specified.)
<BR>
<DT><B>displaySeconds</B>
<DD>If True, control will include a seconds field; if False, it will
just show hours and minutes. (This value is ignored if the <i>format</i>
parameter is specified.)
<BR>
<DT><B>spinButton</B>
<DD>If specified, this button's events will be bound to the behavior of the
@ -151,7 +165,7 @@ set to Jan 1, 1970.) (Same as GetValue(as_wxDateTime=True); provided for backwar
compatibility with previous release.)
<BR>
<BR>
<DT><B>BindSpinButton(wxSpinBtton)</B>
<DT><B>BindSpinButton(SpinBtton)</B>
<DD>Binds an externally created spin button to the control, so that up/down spin
events change the active cell or selection in the control (in addition to the
up/down cursor keys.) (This is primarily to allow you to create a "standard"
@ -258,7 +272,7 @@ import types
import wx
from wx.tools.dbg import Logger
from wx.lib.maskededit import MaskedTextCtrl, Field
from wx.lib.maskededit import BaseMaskedTextCtrl, Field
dbg = Logger()
dbg(enable=0)
@ -281,11 +295,40 @@ class TimeUpdatedEvent(wx.PyCommandEvent):
"""Retrieve the value of the time control at the time this event was generated"""
return self.value
class TimeCtrlAccessorsMixin:
# Define TimeCtrl's list of attributes having their own
# Get/Set functions, ignoring those that make no sense for
# an numeric control.
exposed_basectrl_params = (
'defaultValue',
'description',
class TimeCtrl(MaskedTextCtrl):
'useFixedWidthFont',
'emptyBackgroundColour',
'validBackgroundColour',
'invalidBackgroundColour',
'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 TimeCtrl(BaseMaskedTextCtrl):
valid_ctrl_params = {
'display_seconds' : True, # by default, shows seconds
'format' : 'HHMMSS', # default format code
'displaySeconds' : True, # by default, shows seconds
'min': None, # by default, no bounds set
'max': None,
'limited': False, # by default, no limiting even if bounds set
@ -316,61 +359,39 @@ class TimeCtrl(MaskedTextCtrl):
max = self.__max
limited = self.__limited
self.__posCurrent = 0
# handle deprecated keword argument name:
if kwargs.has_key('display_seconds'):
kwargs['displaySeconds'] = kwargs['display_seconds']
del kwargs['display_seconds']
if not kwargs.has_key('displaySeconds'):
kwargs['displaySeconds'] = True
# (handle positional args (from original release) differently from rest of kwargs:)
self.__fmt24hr = fmt24hr
maskededit_kwargs = {}
# assign keyword args as appropriate:
for key, param_value in kwargs.items():
if key not in TimeCtrl.valid_ctrl_params.keys():
raise AttributeError('invalid keyword argument "%s"' % key)
if key == "display_seconds":
self.__display_seconds = param_value
elif key == "min": min = param_value
elif key == "max": max = param_value
elif key == "limited": limited = param_value
elif key == "useFixedWidthFont":
maskededit_kwargs[key] = param_value
elif key == "oob_color":
maskededit_kwargs['invalidBackgroundColor'] = param_value
if self.__fmt24hr:
if self.__display_seconds: maskededit_kwargs['autoformat'] = 'MILTIMEHHMMSS'
else: maskededit_kwargs['autoformat'] = 'MILTIMEHHMM'
# Set hour field to zero-pad, right-insert, require explicit field change,
# select entire field on entry, and require a resultant valid entry
# to allow character entry:
hourfield = Field(formatcodes='0r<SV', validRegex='0\d|1\d|2[0123]', validRequired=True)
# (handle positional arg (from original release) differently from rest of kwargs:)
self.__fmt24hr = False
if not kwargs.has_key('format'):
if fmt24hr:
if kwargs.has_key('displaySeconds') and kwargs['displaySeconds']:
kwargs['format'] = '24HHMMSS'
del kwargs['displaySeconds']
else:
if self.__display_seconds: maskededit_kwargs['autoformat'] = 'TIMEHHMMSS'
else: maskededit_kwargs['autoformat'] = 'TIMEHHMM'
kwargs['format'] = '24HHMM'
else:
if kwargs.has_key('displaySeconds') and kwargs['displaySeconds']:
kwargs['format'] = 'HHMMSS'
del kwargs['displaySeconds']
else:
kwargs['format'] = 'HHMM'
# Set hour field to allow spaces (at start), right-insert,
# require explicit field change, select entire field on entry,
# and require a resultant valid entry to allow character entry:
hourfield = Field(formatcodes='_0<rSV', validRegex='0[1-9]| [1-9]|1[012]', validRequired=True)
ampmfield = Field(formatcodes='S', emptyInvalid = True, validRequired = True)
if not kwargs.has_key('useFixedWidthFont'):
# allow control over font selection:
kwargs['useFixedWidthFont'] = self.__useFixedWidthFont
# Field 1 is always a zero-padded right-insert minute field,
# similarly configured as above:
minutefield = Field(formatcodes='0r<SV', validRegex='[0-5]\d', validRequired=True)
maskededit_kwargs = self.SetParameters(**kwargs)
fields = [ hourfield, minutefield ]
if self.__display_seconds:
fields.append(copy.copy(minutefield)) # second field has same constraints as field 1
if not self.__fmt24hr:
fields.append(ampmfield)
# set fields argument:
maskededit_kwargs['fields'] = fields
# allow for explicit size specification:
if size != wx.DefaultSize:
# override (and remove) "autofit" autoformat code in standard time formats:
maskededit_kwargs['formatcodes'] = 'T!'
# This allows range validation if set
maskededit_kwargs['validFunc'] = self.IsInBounds
@ -379,16 +400,8 @@ class TimeCtrl(MaskedTextCtrl):
# dynamically without affecting individual field constraint validation
maskededit_kwargs['retainFieldValidation'] = True
# allow control over font selection:
maskededit_kwargs['useFixedWidthFont'] = self.__useFixedWidthFont
# allow for explicit size specification:
if size != wx.DefaultSize:
# override (and remove) "autofit" autoformat code in standard time formats:
maskededit_kwargs['formatcodes'] = 'T!'
# Now we can initialize the base control:
MaskedTextCtrl.__init__(
BaseMaskedTextCtrl.__init__(
self, parent, id=id,
pos=pos, size=size,
style = style,
@ -425,7 +438,7 @@ class TimeCtrl(MaskedTextCtrl):
self.Bind(wx.EVT_LEFT_DCLICK, self._OnDoubleClick ) ## select field under cursor on dclick
self.Bind(wx.EVT_KEY_DOWN, self._OnKeyDown ) ## capture control events not normally seen, eg ctrl-tab.
self.Bind(wx.EVT_CHAR, self.__OnChar ) ## remove "shift" attribute from colon key event,
## then call MaskedTextCtrl._OnChar with
## then call BaseMaskedTextCtrl._OnChar with
## the possibly modified event.
self.Bind(wx.EVT_TEXT, self.__OnTextChange, self ) ## color control appropriately and EVT_TIMEUPDATE events
@ -442,6 +455,110 @@ class TimeCtrl(MaskedTextCtrl):
self.BindSpinButton(spinButton) # bind spin button up/down events to this control
def SetParameters(self, **kwargs):
dbg('TimeCtrl::SetParameters(%s)' % repr(kwargs), indent=1)
maskededit_kwargs = {}
reset_format = False
if kwargs.has_key('display_seconds'):
kwargs['displaySeconds'] = kwargs['display_seconds']
del kwargs['display_seconds']
if kwargs.has_key('format') and kwargs.has_key('displaySeconds'):
del kwargs['displaySeconds'] # always apply format if specified
# assign keyword args as appropriate:
for key, param_value in kwargs.items():
if key not in TimeCtrl.valid_ctrl_params.keys():
raise AttributeError('invalid keyword argument "%s"' % key)
if key == 'format':
# handle both local or generic 'maskededit' autoformat codes:
if param_value == 'HHMMSS' or param_value == 'TIMEHHMMSS':
self.__displaySeconds = True
self.__fmt24hr = False
elif param_value == 'HHMM' or param_value == 'TIMEHHMM':
self.__displaySeconds = False
self.__fmt24hr = False
elif param_value == '24HHMMSS' or param_value == '24HRTIMEHHMMSS':
self.__displaySeconds = True
self.__fmt24hr = True
elif param_value == '24HHMM' or param_value == '24HRTIMEHHMM':
self.__displaySeconds = False
self.__fmt24hr = True
else:
raise AttributeError('"%s" is not a valid format' % param_value)
reset_format = True
elif key in ("displaySeconds", "display_seconds") and not kwargs.has_key('format'):
self.__displaySeconds = param_value
reset_format = True
elif key == "min": min = param_value
elif key == "max": max = param_value
elif key == "limited": limited = param_value
elif key == "useFixedWidthFont":
maskededit_kwargs[key] = param_value
elif key == "oob_color":
maskededit_kwargs['invalidBackgroundColor'] = param_value
if reset_format:
if self.__fmt24hr:
if self.__displaySeconds: maskededit_kwargs['autoformat'] = '24HRTIMEHHMMSS'
else: maskededit_kwargs['autoformat'] = '24HRTIMEHHMM'
# Set hour field to zero-pad, right-insert, require explicit field change,
# select entire field on entry, and require a resultant valid entry
# to allow character entry:
hourfield = Field(formatcodes='0r<SV', validRegex='0\d|1\d|2[0123]', validRequired=True)
else:
if self.__displaySeconds: maskededit_kwargs['autoformat'] = 'TIMEHHMMSS'
else: maskededit_kwargs['autoformat'] = 'TIMEHHMM'
# Set hour field to allow spaces (at start), right-insert,
# require explicit field change, select entire field on entry,
# and require a resultant valid entry to allow character entry:
hourfield = Field(formatcodes='_0<rSV', validRegex='0[1-9]| [1-9]|1[012]', validRequired=True)
ampmfield = Field(formatcodes='S', emptyInvalid = True, validRequired = True)
# Field 1 is always a zero-padded right-insert minute field,
# similarly configured as above:
minutefield = Field(formatcodes='0r<SV', validRegex='[0-5]\d', validRequired=True)
fields = [ hourfield, minutefield ]
if self.__displaySeconds:
fields.append(copy.copy(minutefield)) # second field has same constraints as field 1
if not self.__fmt24hr:
fields.append(ampmfield)
# set fields argument:
maskededit_kwargs['fields'] = fields
# This allows range validation if set
maskededit_kwargs['validFunc'] = self.IsInBounds
# This allows range limits to affect insertion into control or not
# dynamically without affecting individual field constraint validation
maskededit_kwargs['retainFieldValidation'] = True
if hasattr(self, 'controlInitialized') and self.controlInitialized:
self.SetCtrlParameters(**maskededit_kwargs) # set appropriate parameters
# Validate initial value and set if appropriate
try:
self.SetBounds(min, max)
self.SetLimited(limited)
self.SetValue(value)
except:
self.SetValue('12:00:00 AM')
dbg(indent=0)
return {} # no arguments to return
else:
dbg(indent=0)
return maskededit_kwargs
def BindSpinButton(self, sb):
"""
@ -496,7 +613,7 @@ class TimeCtrl(MaskedTextCtrl):
elif as_mxDateTimeDelta:
value = DateTime.DateTimeDelta(0, value.GetHour(), value.GetMinute(), value.GetSecond())
else:
value = MaskedTextCtrl.GetValue(self)
value = BaseMaskedTextCtrl.GetValue(self)
return value
@ -888,6 +1005,16 @@ class TimeCtrl(MaskedTextCtrl):
except ValueError:
return False
def SetFormat(self, format):
self.SetParameters(format=format)
def GetFormat(self):
if self.__displaySeconds:
if self.__fmt24hr: return '24HHMMSS'
else: return 'HHMMSS'
else:
if self.__fmt24hr: return '24HHMM'
else: return 'HHMM'
#-------------------------------------------------------------------------------------------------------------
# these are private functions and overrides:
@ -896,7 +1023,7 @@ class TimeCtrl(MaskedTextCtrl):
def __OnTextChange(self, event=None):
dbg('TimeCtrl::OnTextChange', indent=1)
# Allow wxMaskedtext base control to color as appropriate,
# Allow Maskedtext base control to color as appropriate,
# and Skip the EVT_TEXT event (if appropriate.)
##! WS: For some inexplicable reason, every wxTextCtrl.SetValue()
## call is generating two (2) EVT_TEXT events. (!)
@ -905,7 +1032,7 @@ class TimeCtrl(MaskedTextCtrl):
## event iff the value has actually changed. The masked edit
## OnTextChange routine does this, and returns True on a valid event,
## False otherwise.
if not MaskedTextCtrl._OnTextChange(self, event):
if not BaseMaskedTextCtrl._OnTextChange(self, event):
return
dbg('firing TimeUpdatedEvent...')
@ -922,7 +1049,7 @@ class TimeCtrl(MaskedTextCtrl):
point is lost when the focus shifts to the spin button.
"""
dbg('TimeCtrl::SetInsertionPoint', pos, indent=1)
MaskedTextCtrl.SetInsertionPoint(self, pos) # (causes EVT_TEXT event to fire)
BaseMaskedTextCtrl.SetInsertionPoint(self, pos) # (causes EVT_TEXT event to fire)
self.__posCurrent = self.GetInsertionPoint()
dbg(indent=0)
@ -941,7 +1068,7 @@ class TimeCtrl(MaskedTextCtrl):
sel_to = cell_end
self.__bSelection = sel_start != sel_to
MaskedTextCtrl.SetSelection(self, sel_start, sel_to)
BaseMaskedTextCtrl.SetSelection(self, sel_start, sel_to)
dbg(indent=0)
@ -998,7 +1125,7 @@ class TimeCtrl(MaskedTextCtrl):
if keycode == ord(':'):
dbg('colon seen! removing shift attribute')
event.m_shiftDown = False
MaskedTextCtrl._OnChar(self, event ) ## handle each keypress
BaseMaskedTextCtrl._OnChar(self, event ) ## handle each keypress
dbg(indent=0)
@ -1084,10 +1211,10 @@ class TimeCtrl(MaskedTextCtrl):
converts it to a string appropriate for the format of the control.
"""
if self.__fmt24hr:
if self.__display_seconds: strval = wxdt.Format('%H:%M:%S')
if self.__displaySeconds: strval = wxdt.Format('%H:%M:%S')
else: strval = wxdt.Format('%H:%M')
else:
if self.__display_seconds: strval = wxdt.Format('%I:%M:%S %p')
if self.__displaySeconds: strval = wxdt.Format('%I:%M:%S %p')
else: strval = wxdt.Format('%I:%M %p')
return strval
@ -1178,3 +1305,11 @@ if __name__ == '__main__':
app.MainLoop()
except:
traceback.print_exc()
i=0
## Version 1.2
## 1. Changed parameter name display_seconds to displaySeconds, to follow
## other masked edit conventions.
## 2. Added format parameter, to remove need to use both fmt24hr and displaySeconds.
## 3. Changed inheritance to use BaseMaskedTextCtrl, to remove exposure of
## nonsensical parameter methods from the control, so it will work
## properly with Boa.