Added new MaskedEditControl code from Will Sadkin. The modules are

now locaed in their own sub-package, wx.lib.masked.  Demos updated.


git-svn-id: https://svn.wxwidgets.org/svn/wx/wxWidgets/trunk@26874 c3d73ce0-8a6f-49c7-b76d-6d57e0e08775
This commit is contained in:
Robin Dunn 2004-04-19 23:24:37 +00:00
parent 6cffbf02c0
commit c878ceeae8
17 changed files with 1592 additions and 1420 deletions

View File

@ -4,9 +4,8 @@ import sys
import traceback import traceback
import wx import wx
import wx.lib.maskededit as med import wx.lib.masked as masked
import wx.lib.maskedctrl as mctl import wx.lib.scrolledpanel as scroll
import wx.lib.scrolledpanel as scroll
class demoMixin: class demoMixin:
@ -18,7 +17,7 @@ class demoMixin:
mask = wx.StaticText( self, -1, "Mask Value" ) mask = wx.StaticText( self, -1, "Mask Value" )
formatcode = wx.StaticText( self, -1, "Format" ) formatcode = wx.StaticText( self, -1, "Format" )
regex = wx.StaticText( self, -1, "Regexp Validator(opt.)" ) regex = wx.StaticText( self, -1, "Regexp Validator(opt.)" )
ctrl = wx.StaticText( self, -1, "MaskedTextCtrl" ) ctrl = wx.StaticText( self, -1, "Masked TextCtrl" )
description.SetFont( wx.Font(9, wx.SWISS, wx.NORMAL, wx.BOLD)) description.SetFont( wx.Font(9, wx.SWISS, wx.NORMAL, wx.BOLD))
mask.SetFont( wx.Font(9, wx.SWISS, wx.NORMAL, wx.BOLD)) mask.SetFont( wx.Font(9, wx.SWISS, wx.NORMAL, wx.BOLD))
@ -41,7 +40,7 @@ class demoMixin:
sizer.Add( wx.StaticText( self, -1, control[4]) ) sizer.Add( wx.StaticText( self, -1, control[4]) )
if control in controls: if control in controls:
newControl = med.MaskedTextCtrl( self, -1, "", newControl = masked.TextCtrl( self, -1, "",
mask = control[1], mask = control[1],
excludeChars = control[2], excludeChars = control[2],
formatcodes = control[3], formatcodes = control[3],
@ -79,7 +78,7 @@ class demoPage1(scroll.ScrolledPanel, demoMixin):
self.editList = [] self.editList = []
label = wx.StaticText( self, -1, """\ label = wx.StaticText( self, -1, """\
Here are some basic MaskedTextCtrls to give you an idea of what you can do Here are some basic masked TextCtrls to give you an idea of what you can do
with this control. Note that all controls have been auto-sized by including 'F' in with this control. Note that all controls have been auto-sized by including 'F' in
the format codes. the format codes.
@ -152,8 +151,8 @@ class demoPage2(scroll.ScrolledPanel, demoMixin):
label = wx.StaticText( self, -1, """\ label = wx.StaticText( self, -1, """\
All these controls have been created by passing a single parameter, the autoformat code, All these controls have been created by passing a single parameter, the autoformat code,
and use the factory class MaskedCtrl with its default controlType. and use the factory class masked.Ctrl with its default controlType.
The maskededit module contains an internal dictionary of types and formats (autoformats). The masked package contains an internal dictionary of types and formats (autoformats).
Many of these already do complicated validation; To see some examples, try Many of these already do complicated validation; To see some examples, try
29 Feb 2002 vs. 2004 for the date formats, or email address validation. 29 Feb 2002 vs. 2004 for the date formats, or email address validation.
""") """)
@ -163,7 +162,7 @@ Many of these already do complicated validation; To see some examples, try
description = wx.StaticText( self, -1, "Description") description = wx.StaticText( self, -1, "Description")
autofmt = wx.StaticText( self, -1, "AutoFormat Code") autofmt = wx.StaticText( self, -1, "AutoFormat Code")
ctrl = wx.StaticText( self, -1, "MaskedCtrl") ctrl = wx.StaticText( self, -1, "Masked Ctrl")
description.SetFont( wx.Font( 9, wx.SWISS, wx.NORMAL, wx.BOLD ) ) description.SetFont( wx.Font( 9, wx.SWISS, wx.NORMAL, wx.BOLD ) )
autofmt.SetFont( wx.Font( 9, wx.SWISS, wx.NORMAL, wx.BOLD ) ) autofmt.SetFont( wx.Font( 9, wx.SWISS, wx.NORMAL, wx.BOLD ) )
@ -174,10 +173,10 @@ Many of these already do complicated validation; To see some examples, try
grid.Add( autofmt, 0, wx.ALIGN_LEFT ) grid.Add( autofmt, 0, wx.ALIGN_LEFT )
grid.Add( ctrl, 0, wx.ALIGN_LEFT ) grid.Add( ctrl, 0, wx.ALIGN_LEFT )
for autoformat, desc in med.autoformats: for autoformat, desc in masked.autoformats:
grid.Add( wx.StaticText( self, -1, desc), 0, wx.ALIGN_LEFT ) grid.Add( wx.StaticText( self, -1, desc), 0, wx.ALIGN_LEFT )
grid.Add( wx.StaticText( self, -1, autoformat), 0, wx.ALIGN_LEFT ) grid.Add( wx.StaticText( self, -1, autoformat), 0, wx.ALIGN_LEFT )
grid.Add( mctl.MaskedCtrl( self, -1, "", grid.Add( masked.Ctrl( self, -1, "",
autoformat = autoformat, autoformat = autoformat,
demo = True, demo = True,
name = autoformat), name = autoformat),
@ -197,7 +196,7 @@ class demoPage3(scroll.ScrolledPanel, demoMixin):
self.editList = [] self.editList = []
label = wx.StaticText( self, -1, """\ label = wx.StaticText( self, -1, """\
Here MaskedTextCtrls that have default values. The states Here masked TextCtrls that have default values. The states
control has a list of valid values, and the unsigned integer control has a list of valid values, and the unsigned integer
has a legal range specified. has a legal range specified.
""") """)
@ -215,7 +214,7 @@ has a legal range specified.
controls = [ controls = [
#description mask excl format regexp range,list,initial #description mask excl format regexp range,list,initial
("U.S. State (2 char)", "AA", "", 'F!_', "[A-Z]{2}", '',med.states, med.states[0]), ("U.S. State (2 char)", "AA", "", 'F!_', "[A-Z]{2}", '', masked.states, masked.states[0]),
("Integer (signed)", "#{6}", "", 'F-_', "", '','', ' 0 '), ("Integer (signed)", "#{6}", "", 'F-_', "", '','', ' 0 '),
("Integer (unsigned)\n(1-399)","######", "", 'F_', "", (1,399),'', '1 '), ("Integer (unsigned)\n(1-399)","######", "", 'F_', "", (1,399),'', '1 '),
("Float (signed)", "#{6}.#{9}", "", 'F-_R', "", '','', '000000.000000000'), ("Float (signed)", "#{6}.#{9}", "", 'F-_R', "", '','', '000000.000000000'),
@ -256,7 +255,7 @@ Page Up and Shift-Up arrow will similarly cycle backwards through the list.
description = wx.StaticText( self, -1, "Description" ) description = wx.StaticText( self, -1, "Description" )
autofmt = wx.StaticText( self, -1, "AutoFormat Code" ) autofmt = wx.StaticText( self, -1, "AutoFormat Code" )
fields = wx.StaticText( self, -1, "Field Objects" ) fields = wx.StaticText( self, -1, "Field Objects" )
ctrl = wx.StaticText( self, -1, "MaskedTextCtrl" ) ctrl = wx.StaticText( self, -1, "Masked TextCtrl" )
description.SetFont( wx.Font( 9, wx.SWISS, wx.NORMAL, wx.BOLD ) ) description.SetFont( wx.Font( 9, wx.SWISS, wx.NORMAL, wx.BOLD ) )
autofmt.SetFont( wx.Font( 9, wx.SWISS, wx.NORMAL, wx.BOLD ) ) autofmt.SetFont( wx.Font( 9, wx.SWISS, wx.NORMAL, wx.BOLD ) )
@ -270,7 +269,7 @@ Page Up and Shift-Up arrow will similarly cycle backwards through the list.
grid.Add( ctrl, 0, wx.ALIGN_LEFT ) grid.Add( ctrl, 0, wx.ALIGN_LEFT )
autoformat = "USPHONEFULLEXT" autoformat = "USPHONEFULLEXT"
fieldsDict = {0: med.Field(choices=["617","781","508","978","413"], choiceRequired=True)} fieldsDict = {0: masked.Field(choices=["617","781","508","978","413"], choiceRequired=True)}
fieldsLabel = """\ fieldsLabel = """\
{0: Field(choices=[ {0: Field(choices=[
"617","781", "617","781",
@ -279,7 +278,7 @@ Page Up and Shift-Up arrow will similarly cycle backwards through the list.
grid.Add( wx.StaticText( self, -1, "Restricted Area Code"), 0, wx.ALIGN_LEFT ) grid.Add( wx.StaticText( self, -1, "Restricted Area Code"), 0, wx.ALIGN_LEFT )
grid.Add( wx.StaticText( self, -1, autoformat), 0, wx.ALIGN_LEFT ) grid.Add( wx.StaticText( self, -1, autoformat), 0, wx.ALIGN_LEFT )
grid.Add( wx.StaticText( self, -1, fieldsLabel), 0, wx.ALIGN_LEFT ) grid.Add( wx.StaticText( self, -1, fieldsLabel), 0, wx.ALIGN_LEFT )
grid.Add( med.MaskedTextCtrl( self, -1, "", grid.Add( masked.TextCtrl( self, -1, "",
autoformat = autoformat, autoformat = autoformat,
fields = fieldsDict, fields = fieldsDict,
demo = True, demo = True,
@ -287,12 +286,12 @@ Page Up and Shift-Up arrow will similarly cycle backwards through the list.
0, wx.ALIGN_LEFT ) 0, wx.ALIGN_LEFT )
autoformat = "EXPDATEMMYY" autoformat = "EXPDATEMMYY"
fieldsDict = {1: med.Field(choices=["03", "04", "05"], choiceRequired=True)} fieldsDict = {1: masked.Field(choices=["03", "04", "05"], choiceRequired=True)}
fieldsLabel = """\ fieldsLabel = """\
{1: Field(choices=[ {1: Field(choices=[
"03", "04", "05"], "03", "04", "05"],
choiceRequired=True)}""" choiceRequired=True)}"""
exp = med.MaskedTextCtrl( self, -1, "", exp = masked.TextCtrl( self, -1, "",
autoformat = autoformat, autoformat = autoformat,
fields = fieldsDict, fields = fieldsDict,
demo = True, demo = True,
@ -303,15 +302,15 @@ Page Up and Shift-Up arrow will similarly cycle backwards through the list.
grid.Add( wx.StaticText( self, -1, fieldsLabel), 0, wx.ALIGN_LEFT ) grid.Add( wx.StaticText( self, -1, fieldsLabel), 0, wx.ALIGN_LEFT )
grid.Add( exp, 0, wx.ALIGN_LEFT ) grid.Add( exp, 0, wx.ALIGN_LEFT )
fieldsDict = {0: med.Field(choices=["02134","02155"], choiceRequired=True), fieldsDict = {0: masked.Field(choices=["02134","02155"], choiceRequired=True),
1: med.Field(choices=["1234", "5678"], choiceRequired=False)} 1: masked.Field(choices=["1234", "5678"], choiceRequired=False)}
fieldsLabel = """\ fieldsLabel = """\
{0: Field(choices=["02134","02155"], {0: Field(choices=["02134","02155"],
choiceRequired=True), choiceRequired=True),
1: Field(choices=["1234", "5678"], 1: Field(choices=["1234", "5678"],
choiceRequired=False)}""" choiceRequired=False)}"""
autoformat = "USZIPPLUS4" autoformat = "USZIPPLUS4"
zip = med.MaskedTextCtrl( self, -1, "", zip = masked.TextCtrl( self, -1, "",
autoformat = autoformat, autoformat = autoformat,
fields = fieldsDict, fields = fieldsDict,
demo = True, demo = True,
@ -336,7 +335,7 @@ class demoPage5(scroll.ScrolledPanel, demoMixin):
labelMaskedCombos = wx.StaticText( self, -1, """\ labelMaskedCombos = wx.StaticText( self, -1, """\
These are some examples of MaskedComboBox:""") These are some examples of masked.ComboBox:""")
labelMaskedCombos.SetForegroundColour( "Blue" ) labelMaskedCombos.SetForegroundColour( "Blue" )
@ -344,8 +343,8 @@ These are some examples of MaskedComboBox:""")
A state selector; only A state selector; only
"legal" values can be "legal" values can be
entered:""") entered:""")
statecode = med.MaskedComboBox( self, -1, med.states[0], statecode = masked.ComboBox( self, -1, masked.states[0],
choices = med.states, choices = masked.states,
autoformat="USSTATE") autoformat="USSTATE")
label_statename = wx.StaticText( self, -1, """\ label_statename = wx.StaticText( self, -1, """\
@ -353,9 +352,9 @@ A state name selector,
with auto-select:""") with auto-select:""")
# Create this one using factory function: # Create this one using factory function:
statename = mctl.MaskedCtrl( self, -1, med.state_names[0], statename = masked.Ctrl( self, -1, masked.state_names[0],
controlType = mctl.controlTypes.MASKEDCOMBO, controlType = masked.controlTypes.COMBO,
choices = med.state_names, choices = masked.state_names,
autoformat="USSTATENAME", autoformat="USSTATENAME",
autoSelect=True) autoSelect=True)
statename.SetCtrlParameters(formatcodes = 'F!V_') statename.SetCtrlParameters(formatcodes = 'F!V_')
@ -363,8 +362,8 @@ with auto-select:""")
numerators = [ str(i) for i in range(1, 4) ] numerators = [ str(i) for i in range(1, 4) ]
denominators = [ string.ljust(str(i), 2) for i in [2,3,4,5,8,16,32,64] ] denominators = [ string.ljust(str(i), 2) for i in [2,3,4,5,8,16,32,64] ]
fieldsDict = {0: med.Field(choices=numerators, choiceRequired=False), fieldsDict = {0: masked.Field(choices=numerators, choiceRequired=False),
1: med.Field(choices=denominators, choiceRequired=True)} 1: masked.Field(choices=denominators, choiceRequired=True)}
choices = [] choices = []
for n in numerators: for n in numerators:
for d in denominators: for d in denominators:
@ -377,8 +376,8 @@ A masked ComboBox for fraction selection.
Choices for each side of the fraction can Choices for each side of the fraction can
be selected with PageUp/Down:""") be selected with PageUp/Down:""")
fraction = mctl.MaskedCtrl( self, -1, "", fraction = masked.Ctrl( self, -1, "",
controlType = mctl.MASKEDCOMBO, controlType = masked.controlTypes.COMBO,
choices = choices, choices = choices,
choiceRequired = True, choiceRequired = True,
mask = "#/##", mask = "#/##",
@ -392,7 +391,7 @@ A masked ComboBox to validate
text from a list of numeric codes:""") text from a list of numeric codes:""")
choices = ["91", "136", "305", "4579"] choices = ["91", "136", "305", "4579"]
code = med.MaskedComboBox( self, -1, choices[0], code = masked.ComboBox( self, -1, choices[0],
choices = choices, choices = choices,
choiceRequired = True, choiceRequired = True,
formatcodes = "F_r", formatcodes = "F_r",
@ -402,8 +401,8 @@ text from a list of numeric codes:""")
Programmatically set Programmatically set
choice sets:""") choice sets:""")
self.list_selector = wx.ComboBox(self, -1, '', choices = ['list1', 'list2', 'list3']) self.list_selector = wx.ComboBox(self, -1, '', choices = ['list1', 'list2', 'list3'])
self.dynamicbox = mctl.MaskedCtrl( self, -1, ' ', self.dynamicbox = masked.Ctrl( self, -1, ' ',
controlType = mctl.controlTypes.MASKEDCOMBO, controlType = masked.controlTypes.COMBO,
mask = 'XXXX', mask = 'XXXX',
formatcodes = 'F_', formatcodes = 'F_',
# these are to give dropdown some initial height, # these are to give dropdown some initial height,
@ -415,23 +414,23 @@ choice sets:""")
labelIpAddrs = wx.StaticText( self, -1, """\ labelIpAddrs = wx.StaticText( self, -1, """\
Here are some examples of IpAddrCtrl, a control derived from MaskedTextCtrl:""") Here are some examples of IpAddrCtrl, a control derived from masked.TextCtrl:""")
labelIpAddrs.SetForegroundColour( "Blue" ) labelIpAddrs.SetForegroundColour( "Blue" )
label_ipaddr1 = wx.StaticText( self, -1, "An empty control:") label_ipaddr1 = wx.StaticText( self, -1, "An empty control:")
ipaddr1 = med.IpAddrCtrl( self, -1, style = wx.TE_PROCESS_TAB ) ipaddr1 = masked.IpAddrCtrl( self, -1, style = wx.TE_PROCESS_TAB )
label_ipaddr2 = wx.StaticText( self, -1, "A restricted mask:") label_ipaddr2 = wx.StaticText( self, -1, "A restricted mask:")
ipaddr2 = med.IpAddrCtrl( self, -1, mask=" 10. 1.109.###" ) ipaddr2 = masked.IpAddrCtrl( self, -1, mask=" 10. 1.109.###" )
label_ipaddr3 = wx.StaticText( self, -1, """\ label_ipaddr3 = wx.StaticText( self, -1, """\
A control with restricted legal values: A control with restricted legal values:
10. (1|2) . (129..255) . (0..255)""") 10. (1|2) . (129..255) . (0..255)""")
ipaddr3 = mctl.MaskedCtrl( self, -1, ipaddr3 = masked.Ctrl( self, -1,
controlType = mctl.controlTypes.IPADDR, controlType = masked.controlTypes.IPADDR,
mask=" 10. #.###.###") mask=" 10. #.###.###")
ipaddr3.SetFieldParameters(0, validRegex="1|2",validRequired=False ) # requires entry to match or not allowed ipaddr3.SetFieldParameters(0, validRegex="1|2",validRequired=False ) # requires entry to match or not allowed
@ -441,22 +440,22 @@ A control with restricted legal values:
labelNumerics = wx.StaticText( self, -1, """\ labelNumerics = wx.StaticText( self, -1, """\
Here are some useful configurations of a MaskedTextCtrl for integer and floating point input that still treat Here are some useful configurations of a masked.TextCtrl for integer and floating point input that still treat
the control as a text control. (For a true numeric control, check out the MaskedNumCtrl class!)""") the control as a text control. (For a true numeric control, check out the masked.NumCtrl class!)""")
labelNumerics.SetForegroundColour( "Blue" ) labelNumerics.SetForegroundColour( "Blue" )
label_intctrl1 = wx.StaticText( self, -1, """\ label_intctrl1 = wx.StaticText( self, -1, """\
An integer entry control with An integer entry control with
shifting insert enabled:""") shifting insert enabled:""")
self.intctrl1 = med.MaskedTextCtrl(self, -1, name='intctrl', mask="#{9}", formatcodes = '_-,F>') self.intctrl1 = masked.TextCtrl(self, -1, name='intctrl', mask="#{9}", formatcodes = '_-,F>')
label_intctrl2 = wx.StaticText( self, -1, """\ label_intctrl2 = wx.StaticText( self, -1, """\
Right-insert integer entry:""") Right-insert integer entry:""")
self.intctrl2 = med.MaskedTextCtrl(self, -1, name='intctrl', mask="#{9}", formatcodes = '_-,Fr') self.intctrl2 = masked.TextCtrl(self, -1, name='intctrl', mask="#{9}", formatcodes = '_-,Fr')
label_floatctrl = wx.StaticText( self, -1, """\ label_floatctrl = wx.StaticText( self, -1, """\
A floating point entry control A floating point entry control
with right-insert for ordinal:""") with right-insert for ordinal:""")
self.floatctrl = med.MaskedTextCtrl(self, -1, name='floatctrl', mask="#{9}.#{2}", formatcodes="F,_-R", useParensForNegatives=False) self.floatctrl = masked.TextCtrl(self, -1, name='floatctrl', mask="#{9}.#{2}", formatcodes="F,_-R", useParensForNegatives=False)
self.floatctrl.SetFieldParameters(0, formatcodes='r<', validRequired=True) # right-insert, require explicit cursor movement to change fields self.floatctrl.SetFieldParameters(0, formatcodes='r<', validRequired=True) # right-insert, require explicit cursor movement to change fields
self.floatctrl.SetFieldParameters(1, defaultValue='00') # don't allow blank fraction self.floatctrl.SetFieldParameters(1, defaultValue='00') # don't allow blank fraction
@ -588,7 +587,7 @@ with right-insert for ordinal:""")
formatcodes += 'r' formatcodes += 'r'
mask = '###' mask = '###'
else: else:
choices = med.states choices = masked.states
mask = 'AA' mask = 'AA'
formatcodes += '!' formatcodes += '!'
self.dynamicbox.SetCtrlParameters( mask = mask, self.dynamicbox.SetCtrlParameters( mask = mask,
@ -628,15 +627,15 @@ def runTest(frame, nb, log):
def RunStandalone(): def RunStandalone():
app = wx.PySimpleApp() app = wx.PySimpleApp()
frame = wx.Frame(None, -1, "Test MaskedTextCtrl", size=(640, 480)) frame = wx.Frame(None, -1, "Test MaskedEditCtrls", size=(640, 480))
win = TestMaskedTextCtrls(frame, -1, sys.stdout) win = TestMaskedTextCtrls(frame, -1, sys.stdout)
frame.Show(True) frame.Show(True)
app.MainLoop() app.MainLoop()
#---------------------------------------------------------------------------- #----------------------------------------------------------------------------
import wx.lib.masked.maskededit as maskededit
overview = """<html> overview = """<html>
<PRE><FONT SIZE=-1> <PRE><FONT SIZE=-1>
""" + med.__doc__ + """ """ + maskededit.__doc__ + """
</FONT></PRE> </FONT></PRE>
""" """

View File

@ -4,8 +4,8 @@ import sys
import traceback import traceback
import wx import wx
import wx.lib.maskededit as me from wx.lib import masked
import wx.lib.maskednumctrl as mnum
#---------------------------------------------------------------------- #----------------------------------------------------------------------
class TestPanel( wx.Panel ): class TestPanel( wx.Panel ):
@ -16,40 +16,40 @@ class TestPanel( wx.Panel ):
panel = wx.Panel( self, -1 ) panel = wx.Panel( self, -1 )
header = wx.StaticText(panel, -1, """\ header = wx.StaticText(panel, -1, """\
This shows the various options for MaskedNumCtrl. This shows the various options for masked.NumCtrl.
The controls at the top reconfigure the resulting control at the bottom. The controls at the top reconfigure the resulting control at the bottom.
""") """)
header.SetForegroundColour( "Blue" ) header.SetForegroundColour( "Blue" )
intlabel = wx.StaticText( panel, -1, "Integer width:" ) intlabel = wx.StaticText( panel, -1, "Integer width:" )
self.integerwidth = mnum.MaskedNumCtrl( self.integerwidth = masked.NumCtrl(
panel, value=10, integerWidth=2, allowNegative=False panel, value=10, integerWidth=2, allowNegative=False
) )
fraclabel = wx.StaticText( panel, -1, "Fraction width:" ) fraclabel = wx.StaticText( panel, -1, "Fraction width:" )
self.fractionwidth = mnum.MaskedNumCtrl( self.fractionwidth = masked.NumCtrl(
panel, value=0, integerWidth=2, allowNegative=False panel, value=0, integerWidth=2, allowNegative=False
) )
groupcharlabel = wx.StaticText( panel,-1, "Grouping char:" ) groupcharlabel = wx.StaticText( panel,-1, "Grouping char:" )
self.groupchar = me.MaskedTextCtrl( self.groupchar = masked.TextCtrl(
panel, -1, value=',', mask='&', excludeChars = '-()', panel, -1, value=',', mask='&', excludeChars = '-()',
formatcodes='F', emptyInvalid=True, validRequired=True formatcodes='F', emptyInvalid=True, validRequired=True
) )
decimalcharlabel = wx.StaticText( panel,-1, "Decimal char:" ) decimalcharlabel = wx.StaticText( panel,-1, "Decimal char:" )
self.decimalchar = me.MaskedTextCtrl( self.decimalchar = masked.TextCtrl(
panel, -1, value='.', mask='&', excludeChars = '-()', panel, -1, value='.', mask='&', excludeChars = '-()',
formatcodes='F', emptyInvalid=True, validRequired=True formatcodes='F', emptyInvalid=True, validRequired=True
) )
self.set_min = wx.CheckBox( panel, -1, "Set minimum value:" ) self.set_min = wx.CheckBox( panel, -1, "Set minimum value:" )
# Create this MaskedNumCtrl using factory, to show how: # Create this masked.NumCtrl using factory, to show how:
self.min = mnum.MaskedNumCtrl( panel, integerWidth=5, fractionWidth=2 ) self.min = masked.Ctrl( panel, integerWidth=5, fractionWidth=2, controlType=masked.controlTypes.NUMBER )
self.min.Enable( False ) self.min.Enable( False )
self.set_max = wx.CheckBox( panel, -1, "Set maximum value:" ) self.set_max = wx.CheckBox( panel, -1, "Set maximum value:" )
self.max = mnum.MaskedNumCtrl( panel, integerWidth=5, fractionWidth=2 ) self.max = masked.NumCtrl( panel, integerWidth=5, fractionWidth=2 )
self.max.Enable( False ) self.max.Enable( False )
@ -68,7 +68,7 @@ The controls at the top reconfigure the resulting control at the bottom.
font.SetWeight(wx.BOLD) font.SetWeight(wx.BOLD)
label.SetFont(font) label.SetFont(font)
self.target_ctl = mnum.MaskedNumCtrl( panel, -1, name="target control" ) self.target_ctl = masked.NumCtrl( panel, -1, name="target control" )
label_numselect = wx.StaticText( panel, -1, """\ label_numselect = wx.StaticText( panel, -1, """\
Programmatically set the above Programmatically set the above
@ -141,15 +141,15 @@ value entry ctrl:""")
panel.Move( (50,10) ) panel.Move( (50,10) )
self.panel = panel self.panel = panel
self.Bind(mnum.EVT_MASKEDNUM, self.OnSetIntWidth, self.integerwidth ) self.Bind(masked.EVT_NUM, self.OnSetIntWidth, self.integerwidth )
self.Bind(mnum.EVT_MASKEDNUM, self.OnSetFractionWidth, self.fractionwidth ) self.Bind(masked.EVT_NUM, self.OnSetFractionWidth, self.fractionwidth )
self.Bind(wx.EVT_TEXT, self.OnSetGroupChar, self.groupchar ) self.Bind(wx.EVT_TEXT, self.OnSetGroupChar, self.groupchar )
self.Bind(wx.EVT_TEXT, self.OnSetDecimalChar, self.decimalchar ) self.Bind(wx.EVT_TEXT, self.OnSetDecimalChar, self.decimalchar )
self.Bind(wx.EVT_CHECKBOX, self.OnSetMin, self.set_min ) self.Bind(wx.EVT_CHECKBOX, self.OnSetMin, self.set_min )
self.Bind(wx.EVT_CHECKBOX, self.OnSetMax, self.set_max ) self.Bind(wx.EVT_CHECKBOX, self.OnSetMax, self.set_max )
self.Bind(mnum.EVT_MASKEDNUM, self.SetTargetMinMax, self.min ) self.Bind(masked.EVT_NUM, self.SetTargetMinMax, self.min )
self.Bind(mnum.EVT_MASKEDNUM, self.SetTargetMinMax, self.max ) self.Bind(masked.EVT_NUM, self.SetTargetMinMax, self.max )
self.Bind(wx.EVT_CHECKBOX, self.SetTargetMinMax, self.limit_target ) self.Bind(wx.EVT_CHECKBOX, self.SetTargetMinMax, self.limit_target )
self.Bind(wx.EVT_CHECKBOX, self.OnSetAllowNone, self.allow_none ) self.Bind(wx.EVT_CHECKBOX, self.OnSetAllowNone, self.allow_none )
@ -158,7 +158,7 @@ value entry ctrl:""")
self.Bind(wx.EVT_CHECKBOX, self.OnSetUseParens, self.use_parens ) self.Bind(wx.EVT_CHECKBOX, self.OnSetUseParens, self.use_parens )
self.Bind(wx.EVT_CHECKBOX, self.OnSetSelectOnEntry, self.select_on_entry ) self.Bind(wx.EVT_CHECKBOX, self.OnSetSelectOnEntry, self.select_on_entry )
self.Bind(mnum.EVT_MASKEDNUM, self.OnTargetChange, self.target_ctl ) self.Bind(masked.EVT_NUM, self.OnTargetChange, self.target_ctl )
self.Bind(wx.EVT_COMBOBOX, self.OnNumberSelect, self.numselect ) self.Bind(wx.EVT_COMBOBOX, self.OnNumberSelect, self.numselect )
@ -323,6 +323,7 @@ def runTest( frame, nb, log ):
return win return win
#---------------------------------------------------------------------- #----------------------------------------------------------------------
import wx.lib.masked.numctrl as mnum
overview = mnum.__doc__ overview = mnum.__doc__
if __name__ == '__main__': if __name__ == '__main__':

View File

@ -1,12 +1,12 @@
# #
# 11/21/2003 - Jeff Grimmett (grimmtooth@softhome.net) # 11/21/2003 - Jeff Grimmett (grimmtooth@softhome.net)
# #
# o presense of spin control causing probs (see spin ctrl demo for details) # o presense of spin control causing probs (see spin ctrl demo for details)
# #
import wx import wx
import wx.lib.timectrl as timectl import wx.lib.scrolledpanel as scrolled
import wx.lib.scrolledpanel as scrolled import wx.lib.masked as masked
#---------------------------------------------------------------------- #----------------------------------------------------------------------
@ -18,21 +18,21 @@ class TestPanel( scrolled.ScrolledPanel ):
text1 = wx.StaticText( self, -1, "12-hour format:") text1 = wx.StaticText( self, -1, "12-hour format:")
self.time12 = timectl.TimeCtrl( self, -1, name="12 hour control" ) self.time12 = masked.TimeCtrl( self, -1, name="12 hour control" )
spin1 = wx.SpinButton( self, -1, wx.DefaultPosition, (-1,20), 0 ) spin1 = wx.SpinButton( self, -1, wx.DefaultPosition, (-1,20), 0 )
self.time12.BindSpinButton( spin1 ) self.time12.BindSpinButton( spin1 )
text2 = wx.StaticText( self, -1, "24-hour format:") text2 = wx.StaticText( self, -1, "24-hour format:")
spin2 = wx.SpinButton( self, -1, wx.DefaultPosition, (-1,20), 0 ) spin2 = wx.SpinButton( self, -1, wx.DefaultPosition, (-1,20), 0 )
self.time24 = timectl.TimeCtrl( self.time24 = masked.TimeCtrl(
self, -1, name="24 hour control", fmt24hr=True, self, -1, name="24 hour control", fmt24hr=True,
spinButton = spin2 spinButton = spin2
) )
text3 = wx.StaticText( self, -1, "No seconds\nor spin button:") text3 = wx.StaticText( self, -1, "No seconds\nor spin button:")
self.spinless_ctrl = timectl.TimeCtrl( self.spinless_ctrl = masked.TimeCtrl(
self, -1, name="spinless control", self, -1, name="spinless control",
display_seconds = False display_seconds = False
) )
grid = wx.FlexGridSizer( 0, 2, 10, 5 ) grid = wx.FlexGridSizer( 0, 2, 10, 5 )
@ -54,8 +54,8 @@ class TestPanel( scrolled.ScrolledPanel ):
buttonChange = wx.Button( self, -1, "Change Controls") buttonChange = wx.Button( self, -1, "Change Controls")
self.radio12to24 = wx.RadioButton( self.radio12to24 = wx.RadioButton(
self, -1, "Copy 12-hour time to 24-hour control", self, -1, "Copy 12-hour time to 24-hour control",
wx.DefaultPosition, wx.DefaultSize, wx.RB_GROUP wx.DefaultPosition, wx.DefaultSize, wx.RB_GROUP
) )
self.radio24to12 = wx.RadioButton( self.radio24to12 = wx.RadioButton(
@ -86,17 +86,17 @@ class TestPanel( scrolled.ScrolledPanel ):
self.set_bounds = wx.CheckBox( self, -1, "Set time bounds:" ) self.set_bounds = wx.CheckBox( self, -1, "Set time bounds:" )
minlabel = wx.StaticText( self, -1, "minimum time:" ) minlabel = wx.StaticText( self, -1, "minimum time:" )
self.min = timectl.TimeCtrl( self, -1, name="min", display_seconds = False ) self.min = masked.TimeCtrl( self, -1, name="min", display_seconds = False )
self.min.Enable( False ) self.min.Enable( False )
maxlabel = wx.StaticText( self, -1, "maximum time:" ) maxlabel = wx.StaticText( self, -1, "maximum time:" )
self.max = timectl.TimeCtrl( self, -1, name="max", display_seconds = False ) self.max = masked.TimeCtrl( self, -1, name="max", display_seconds = False )
self.max.Enable( False ) self.max.Enable( False )
self.limit_check = wx.CheckBox( self, -1, "Limit control" ) self.limit_check = wx.CheckBox( self, -1, "Limit control" )
label = wx.StaticText( self, -1, "Resulting time control:" ) label = wx.StaticText( self, -1, "Resulting time control:" )
self.target_ctrl = timectl.TimeCtrl( self, -1, name="new" ) self.target_ctrl = masked.TimeCtrl( self, -1, name="new" )
grid2 = wx.FlexGridSizer( 0, 2, 0, 0 ) grid2 = wx.FlexGridSizer( 0, 2, 0, 0 )
grid2.Add( (20, 0), 0, wx.ALIGN_LEFT|wx.ALL, 5 ) grid2.Add( (20, 0), 0, wx.ALIGN_LEFT|wx.ALL, 5 )
@ -142,14 +142,14 @@ class TestPanel( scrolled.ScrolledPanel ):
self.SetupScrolling() self.SetupScrolling()
self.Bind(wx.EVT_BUTTON, self.OnButtonClick, buttonChange ) self.Bind(wx.EVT_BUTTON, self.OnButtonClick, buttonChange )
self.Bind(timectl.EVT_TIMEUPDATE, self.OnTimeChange, self.time12 ) self.Bind(masked.EVT_TIMEUPDATE, self.OnTimeChange, self.time12 )
self.Bind(timectl.EVT_TIMEUPDATE, self.OnTimeChange, self.time24 ) self.Bind(masked.EVT_TIMEUPDATE, self.OnTimeChange, self.time24 )
self.Bind(timectl.EVT_TIMEUPDATE, self.OnTimeChange, self.spinless_ctrl ) self.Bind(masked.EVT_TIMEUPDATE, self.OnTimeChange, self.spinless_ctrl )
self.Bind(wx.EVT_CHECKBOX, self.OnBoundsCheck, self.set_bounds ) self.Bind(wx.EVT_CHECKBOX, self.OnBoundsCheck, self.set_bounds )
self.Bind(wx.EVT_CHECKBOX, self.SetTargetMinMax, self.limit_check ) self.Bind(wx.EVT_CHECKBOX, self.SetTargetMinMax, self.limit_check )
self.Bind(timectl.EVT_TIMEUPDATE, self.SetTargetMinMax, self.min ) self.Bind(masked.EVT_TIMEUPDATE, self.SetTargetMinMax, self.min )
self.Bind(timectl.EVT_TIMEUPDATE, self.SetTargetMinMax, self.max ) self.Bind(masked.EVT_TIMEUPDATE, self.SetTargetMinMax, self.max )
self.Bind(timectl.EVT_TIMEUPDATE, self.OnTimeChange, self.target_ctrl ) self.Bind(masked.EVT_TIMEUPDATE, self.OnTimeChange, self.target_ctrl )
def OnTimeChange( self, event ): def OnTimeChange( self, event ):
@ -204,7 +204,7 @@ class TestPanel( scrolled.ScrolledPanel ):
min, max = None, None min, max = None, None
cur_min, cur_max = self.target_ctrl.GetBounds() cur_min, cur_max = self.target_ctrl.GetBounds()
print cur_min, min
if min and (min != cur_min): self.target_ctrl.SetMin( min ) if min and (min != cur_min): self.target_ctrl.SetMin( min )
if max and (max != cur_max): self.target_ctrl.SetMax( max ) if max and (max != cur_max): self.target_ctrl.SetMax( max )
@ -225,11 +225,11 @@ def runTest( frame, nb, log ):
return win return win
#---------------------------------------------------------------------- #----------------------------------------------------------------------
import wx.lib.masked.timectrl as timectl
overview = timectl.__doc__ overview = timectl.__doc__
if __name__ == '__main__': if __name__ == '__main__':
import sys,os import sys,os
import run import run
run.main(['', os.path.basename(sys.argv[0])] + sys.argv[1:]) run.main(['', os.path.basename(sys.argv[0])])

View File

@ -17,6 +17,9 @@ Added some convenience methods to wx.Bitmap: SetSize, GetSize, and
wx.EmptyBitmap can be called with a wx.Size (or a 2-element sequence) wx.EmptyBitmap can be called with a wx.Size (or a 2-element sequence)
object too. Similar changes were done for wx.Image as well. object too. Similar changes were done for wx.Image as well.
Added new MaskedEditControl code from Will Sadkin. The modules are
now locaed in their own sub-package, wx.lib.masked. Demos updated.

View File

@ -619,6 +619,14 @@ Similarly, the wxSystemSettings backwards compatibiility aliases for
GetSystemColour, GetSystemFont and GetSystemMetric have also gone into GetSystemColour, GetSystemFont and GetSystemMetric have also gone into
the bit-bucket. Use GetColour, GetFont and GetMetric instead. the bit-bucket. Use GetColour, GetFont and GetMetric instead.
Use the Python True/False constants instead of the true, TRUE, false,
FALSE that used to be provided with wxPython.
Use None instead of the ancient and should have been removed a long
time ago wx.NULL alias.
wx.TreeCtrl no longer needs to be passed the cookie variable as the
2nd parameter. It still returns it though, for use with GetNextChild.
The wx.NO_FULL_REPAINT_ON_RESIZE style is now the default style for The wx.NO_FULL_REPAINT_ON_RESIZE style is now the default style for
all windows. The name still exists for compatibility, but it is set all windows. The name still exists for compatibility, but it is set
@ -667,3 +675,8 @@ functions in wxPython for parameters that are expecting an integer.
If the object is not already an integer then it will be asked to If the object is not already an integer then it will be asked to
convert itself to one. A similar conversion fragment is in place for convert itself to one. A similar conversion fragment is in place for
parameters that expect floating point values. parameters that expect floating point values.
**[Changed in 2.5.1.6]** The MaskedEditCtrl modules have been moved
to their own sub-package, wx.lib.masked. See the docstrings and demo
for changes in capabilities, usage, etc.

View File

@ -0,0 +1,20 @@
#----------------------------------------------------------------------
# Name: wxPython.lib.masked
# Purpose: A package containing the masked edit controls
#
# Author: Will Sadkin, Jeff Childers
#
# Created: 6-Mar-2004
# RCS-ID: $Id$
# Copyright: (c) 2004
# License: wxWidgets license
#----------------------------------------------------------------------
# import relevant external symbols into package namespace:
from maskededit import *
from textctrl import BaseMaskedTextCtrl, TextCtrl
from combobox import BaseMaskedComboBox, ComboBox, MaskedComboBoxSelectEvent
from numctrl import NumCtrl, wxEVT_COMMAND_MASKED_NUMBER_UPDATED, EVT_NUM, NumberUpdatedEvent
from timectrl import TimeCtrl, wxEVT_TIMEVAL_UPDATED, EVT_TIMEUPDATE, TimeUpdatedEvent
from ipaddrctrl import IpAddrCtrl
from ctrl import Ctrl, controlTypes

View File

@ -0,0 +1,540 @@
#----------------------------------------------------------------------------
# Name: masked.combobox.py
# Authors: Will Sadkin
# Email: wsadkin@nameconnector.com
# Created: 02/11/2003
# Copyright: (c) 2003 by Will Sadkin, 2003
# RCS-ID: $Id$
# License: wxWidgets license
#----------------------------------------------------------------------------
#
# This masked edit class allows for the semantics of masked controls
# to be applied to combo boxes.
#
#----------------------------------------------------------------------------
import wx
from wx.lib.masked import *
# jmg 12/9/03 - when we cut ties with Py 2.2 and earlier, this would
# be a good place to implement the 2.3 logger class
from wx.tools.dbg import Logger
dbg = Logger()
##dbg(enable=0)
## ---------- ---------- ---------- ---------- ---------- ---------- ----------
## Because calling SetSelection programmatically does not fire EVT_COMBOBOX
## events, we have to do it ourselves when we auto-complete.
class MaskedComboBoxSelectEvent(wx.PyCommandEvent):
def __init__(self, id, selection = 0, object=None):
wx.PyCommandEvent.__init__(self, wx.wxEVT_COMMAND_COMBOBOX_SELECTED, id)
self.__selection = selection
self.SetEventObject(object)
def GetSelection(self):
"""Retrieve the value of the control at the time
this event was generated."""
return self.__selection
class BaseMaskedComboBox( wx.ComboBox, MaskedEditMixin ):
"""
This masked edit control adds the ability to use a masked input
on a combobox, and do auto-complete of such values.
"""
def __init__( self, parent, id=-1, value = '',
pos = wx.DefaultPosition,
size = wx.DefaultSize,
choices = [],
style = wx.CB_DROPDOWN,
validator = wx.DefaultValidator,
name = "maskedComboBox",
setupEventHandling = True, ## setup event handling by default):
**kwargs):
# This is necessary, because wxComboBox currently provides no
# method for determining later if this was specified in the
# constructor for the control...
self.__readonly = style & wx.CB_READONLY == wx.CB_READONLY
kwargs['choices'] = choices ## set up maskededit to work with choice list too
## Since combobox completion is case-insensitive, always validate same way
if not kwargs.has_key('compareNoCase'):
kwargs['compareNoCase'] = True
MaskedEditMixin.__init__( self, name, **kwargs )
self._choices = self._ctrl_constraints._choices
## dbg('self._choices:', self._choices)
if self._ctrl_constraints._alignRight:
choices = [choice.rjust(self._masklength) for choice in choices]
else:
choices = [choice.ljust(self._masklength) for choice in choices]
wx.ComboBox.__init__(self, parent, id, value='',
pos=pos, size = size,
choices=choices, style=style|wx.WANTS_CHARS,
validator=validator,
name=name)
self.controlInitialized = True
# Set control font - fixed width by default
self._setFont()
if self._autofit:
self.SetClientSize(self._CalcSize())
if value:
# ensure value is width of the mask of the control:
if self._ctrl_constraints._alignRight:
value = value.rjust(self._masklength)
else:
value = value.ljust(self._masklength)
if self.__readonly:
self.SetStringSelection(value)
else:
self._SetInitialValue(value)
self._SetKeycodeHandler(wx.WXK_UP, self.OnSelectChoice)
self._SetKeycodeHandler(wx.WXK_DOWN, self.OnSelectChoice)
if setupEventHandling:
## Setup event handlers
self.Bind(wx.EVT_SET_FOCUS, self._OnFocus ) ## defeat automatic full selection
self.Bind(wx.EVT_KILL_FOCUS, self._OnKillFocus ) ## run internal validator
self.Bind(wx.EVT_LEFT_DCLICK, self._OnDoubleClick) ## select field under cursor on dclick
self.Bind(wx.EVT_RIGHT_UP, self._OnContextMenu ) ## bring up an appropriate context menu
self.Bind(wx.EVT_CHAR, self._OnChar ) ## handle each keypress
self.Bind(wx.EVT_KEY_DOWN, self.OnKeyDown ) ## for special processing of up/down keys
self.Bind(wx.EVT_KEY_DOWN, self._OnKeyDown ) ## for processing the rest of the control keys
## (next in evt chain)
self.Bind(wx.EVT_TEXT, self._OnTextChange ) ## color control appropriately & keep
## track of previous value for undo
def __repr__(self):
return "<MaskedComboBox: %s>" % self.GetValue()
def _CalcSize(self, size=None):
"""
Calculate automatic size if allowed; augment base mixin function
to account for the selector button.
"""
size = self._calcSize(size)
return (size[0]+20, size[1])
def _GetSelection(self):
"""
Allow mixin to get the text selection of this control.
REQUIRED by any class derived from MaskedEditMixin.
"""
return self.GetMark()
def _SetSelection(self, sel_start, sel_to):
"""
Allow mixin to set the text selection of this control.
REQUIRED by any class derived from MaskedEditMixin.
"""
return self.SetMark( sel_start, sel_to )
def _GetInsertionPoint(self):
return self.GetInsertionPoint()
def _SetInsertionPoint(self, pos):
self.SetInsertionPoint(pos)
def _GetValue(self):
"""
Allow mixin to get the raw value of the control with this function.
REQUIRED by any class derived from MaskedEditMixin.
"""
return self.GetValue()
def _SetValue(self, value):
"""
Allow mixin to set the raw value of the control with this function.
REQUIRED by any class derived from MaskedEditMixin.
"""
# For wxComboBox, ensure that values are properly padded so that
# if varying length choices are supplied, they always show up
# in the window properly, and will be the appropriate length
# to match the mask:
if self._ctrl_constraints._alignRight:
value = value.rjust(self._masklength)
else:
value = value.ljust(self._masklength)
# Record current selection and insertion point, for undo
self._prevSelection = self._GetSelection()
self._prevInsertionPoint = self._GetInsertionPoint()
wx.ComboBox.SetValue(self, value)
# text change events don't always fire, so we check validity here
# to make certain formatting is applied:
self._CheckValid()
def SetValue(self, value):
"""
This function redefines the externally accessible .SetValue to be
a smart "paste" of the text in question, so as not to corrupt the
masked control. NOTE: this must be done in the class derived
from the base wx control.
"""
if not self._mask:
wx.ComboBox.SetValue(value) # revert to base control behavior
return
# else...
# empty previous contents, replacing entire value:
self._SetInsertionPoint(0)
self._SetSelection(0, self._masklength)
if( len(value) < self._masklength # value shorter than control
and (self._isFloat or self._isInt) # and it's a numeric control
and self._ctrl_constraints._alignRight ): # and it's a right-aligned control
# try to intelligently "pad out" the value to the right size:
value = self._template[0:self._masklength - len(value)] + value
## dbg('padded value = "%s"' % value)
# For wxComboBox, ensure that values are properly padded so that
# if varying length choices are supplied, they always show up
# in the window properly, and will be the appropriate length
# to match the mask:
elif self._ctrl_constraints._alignRight:
value = value.rjust(self._masklength)
else:
value = value.ljust(self._masklength)
# make SetValue behave the same as if you had typed the value in:
try:
value = self._Paste(value, raise_on_invalid=True, just_return_value=True)
if self._isFloat:
self._isNeg = False # (clear current assumptions)
value = self._adjustFloat(value)
elif self._isInt:
self._isNeg = False # (clear current assumptions)
value = self._adjustInt(value)
elif self._isDate and not self.IsValid(value) and self._4digityear:
value = self._adjustDate(value, fixcentury=True)
except ValueError:
# If date, year might be 2 digits vs. 4; try adjusting it:
if self._isDate and self._4digityear:
dateparts = value.split(' ')
dateparts[0] = self._adjustDate(dateparts[0], fixcentury=True)
value = string.join(dateparts, ' ')
## dbg('adjusted value: "%s"' % value)
value = self._Paste(value, raise_on_invalid=True, just_return_value=True)
else:
raise
self._SetValue(value)
#### dbg('queuing insertion after .SetValue', self._masklength)
wx.CallAfter(self._SetInsertionPoint, self._masklength)
wx.CallAfter(self._SetSelection, self._masklength, self._masklength)
def _Refresh(self):
"""
Allow mixin to refresh the base control with this function.
REQUIRED by any class derived from MaskedEditMixin.
"""
wx.ComboBox.Refresh(self)
def Refresh(self):
"""
This function redefines the externally accessible .Refresh() to
validate the contents of the masked control as it refreshes.
NOTE: this must be done in the class derived from the base wx control.
"""
self._CheckValid()
self._Refresh()
def _IsEditable(self):
"""
Allow mixin to determine if the base control is editable with this function.
REQUIRED by any class derived from MaskedEditMixin.
"""
return not self.__readonly
def Cut(self):
"""
This function redefines the externally accessible .Cut to be
a smart "erase" of the text in question, so as not to corrupt the
masked control. NOTE: this must be done in the class derived
from the base wx control.
"""
if self._mask:
self._Cut() # call the mixin's Cut method
else:
wx.ComboBox.Cut(self) # else revert to base control behavior
def Paste(self):
"""
This function redefines the externally accessible .Paste to be
a smart "paste" of the text in question, so as not to corrupt the
masked control. NOTE: this must be done in the class derived
from the base wx control.
"""
if self._mask:
self._Paste() # call the mixin's Paste method
else:
wx.ComboBox.Paste(self) # else revert to base control behavior
def Undo(self):
"""
This function defines the undo operation for the control. (The default
undo is 1-deep.)
"""
if self._mask:
self._Undo()
else:
wx.ComboBox.Undo() # else revert to base control behavior
def Append( self, choice, clientData=None ):
"""
This function override is necessary so we can keep track of any additions to the list
of choices, because wxComboBox doesn't have an accessor for the choice list.
The code here is the same as in the SetParameters() mixin function, but is
done for the individual value as appended, so the list can be built incrementally
without speed penalty.
"""
if self._mask:
if type(choice) not in (types.StringType, types.UnicodeType):
raise TypeError('%s: choices must be a sequence of strings' % str(self._index))
elif not self.IsValid(choice):
raise ValueError('%s: "%s" is not a valid value for the control as specified.' % (str(self._index), choice))
if not self._ctrl_constraints._choices:
self._ctrl_constraints._compareChoices = []
self._ctrl_constraints._choices = []
self._hasList = True
compareChoice = choice.strip()
if self._ctrl_constraints._compareNoCase:
compareChoice = compareChoice.lower()
if self._ctrl_constraints._alignRight:
choice = choice.rjust(self._masklength)
else:
choice = choice.ljust(self._masklength)
if self._ctrl_constraints._fillChar != ' ':
choice = choice.replace(' ', self._fillChar)
## dbg('updated choice:', choice)
self._ctrl_constraints._compareChoices.append(compareChoice)
self._ctrl_constraints._choices.append(choice)
self._choices = self._ctrl_constraints._choices # (for shorthand)
if( not self.IsValid(choice) and
(not self._ctrl_constraints.IsEmpty(choice) or
(self._ctrl_constraints.IsEmpty(choice) and self._ctrl_constraints._validRequired) ) ):
raise ValueError('"%s" is not a valid value for the control "%s" as specified.' % (choice, self.name))
wx.ComboBox.Append(self, choice, clientData)
def Clear( self ):
"""
This function override is necessary so we can keep track of any additions to the list
of choices, because wxComboBox doesn't have an accessor for the choice list.
"""
if self._mask:
self._choices = []
self._ctrl_constraints._autoCompleteIndex = -1
if self._ctrl_constraints._choices:
self.SetCtrlParameters(choices=[])
wx.ComboBox.Clear(self)
def _OnCtrlParametersChanged(self):
"""
Override mixin's default OnCtrlParametersChanged to detect changes in choice list, so
we can update the base control:
"""
if self.controlInitialized and self._choices != self._ctrl_constraints._choices:
wx.ComboBox.Clear(self)
self._choices = self._ctrl_constraints._choices
for choice in self._choices:
wx.ComboBox.Append( self, choice )
def GetMark(self):
"""
This function is a hack to make up for the fact that wxComboBox has no
method for returning the selected portion of its edit control. It
works, but has the nasty side effect of generating lots of intermediate
events.
"""
## dbg(suspend=1) # turn off debugging around this function
## dbg('MaskedComboBox::GetMark', indent=1)
if self.__readonly:
## dbg(indent=0)
return 0, 0 # no selection possible for editing
## sel_start, sel_to = wxComboBox.GetMark(self) # what I'd *like* to have!
sel_start = sel_to = self.GetInsertionPoint()
## dbg("current sel_start:", sel_start)
value = self.GetValue()
## dbg('value: "%s"' % value)
self._ignoreChange = True # tell _OnTextChange() to ignore next event (if any)
wx.ComboBox.Cut(self)
newvalue = self.GetValue()
## dbg("value after Cut operation:", newvalue)
if newvalue != value: # something was selected; calculate extent
## dbg("something selected")
sel_to = sel_start + len(value) - len(newvalue)
wx.ComboBox.SetValue(self, value) # restore original value and selection (still ignoring change)
wx.ComboBox.SetInsertionPoint(self, sel_start)
wx.ComboBox.SetMark(self, sel_start, sel_to)
self._ignoreChange = False # tell _OnTextChange() to pay attn again
## dbg('computed selection:', sel_start, sel_to, indent=0, suspend=0)
return sel_start, sel_to
def SetSelection(self, index):
"""
Necessary for bookkeeping on choice selection, to keep current value
current.
"""
## dbg('MaskedComboBox::SetSelection(%d)' % index)
if self._mask:
self._prevValue = self._curValue
self._curValue = self._choices[index]
self._ctrl_constraints._autoCompleteIndex = index
wx.ComboBox.SetSelection(self, index)
def OnKeyDown(self, event):
"""
This function is necessary because navigation and control key
events do not seem to normally be seen by the wxComboBox's
EVT_CHAR routine. (Tabs don't seem to be visible no matter
what... {:-( )
"""
if event.GetKeyCode() in self._nav + self._control:
self._OnChar(event)
return
else:
event.Skip() # let mixin default KeyDown behavior occur
def OnSelectChoice(self, event):
"""
This function appears to be necessary, because the processing done
on the text of the control somehow interferes with the combobox's
selection mechanism for the arrow keys.
"""
## dbg('MaskedComboBox::OnSelectChoice', indent=1)
if not self._mask:
event.Skip()
return
value = self.GetValue().strip()
if self._ctrl_constraints._compareNoCase:
value = value.lower()
if event.GetKeyCode() == wx.WXK_UP:
direction = -1
else:
direction = 1
match_index, partial_match = self._autoComplete(
direction,
self._ctrl_constraints._compareChoices,
value,
self._ctrl_constraints._compareNoCase,
current_index = self._ctrl_constraints._autoCompleteIndex)
if match_index is not None:
## dbg('setting selection to', match_index)
# issue appropriate event to outside:
self._OnAutoSelect(self._ctrl_constraints, match_index=match_index)
self._CheckValid()
keep_processing = False
else:
pos = self._adjustPos(self._GetInsertionPoint(), event.GetKeyCode())
field = self._FindField(pos)
if self.IsEmpty() or not field._hasList:
## dbg('selecting 1st value in list')
self._OnAutoSelect(self._ctrl_constraints, match_index=0)
self._CheckValid()
keep_processing = False
else:
# attempt field-level auto-complete
## dbg(indent=0)
keep_processing = self._OnAutoCompleteField(event)
## dbg('keep processing?', keep_processing, indent=0)
return keep_processing
def _OnAutoSelect(self, field, match_index):
"""
Override mixin (empty) autocomplete handler, so that autocompletion causes
combobox to update appropriately.
"""
## dbg('MaskedComboBox::OnAutoSelect', field._index, indent=1)
## field._autoCompleteIndex = match_index
if field == self._ctrl_constraints:
self.SetSelection(match_index)
## dbg('issuing combo selection event')
self.GetEventHandler().ProcessEvent(
MaskedComboBoxSelectEvent( self.GetId(), match_index, self ) )
self._CheckValid()
## dbg('field._autoCompleteIndex:', match_index)
## dbg('self.GetSelection():', self.GetSelection())
## dbg(indent=0)
def _OnReturn(self, event):
"""
For wxComboBox, it seems that if you hit return when the dropdown is
dropped, the event that dismisses the dropdown will also blank the
control, because of the implementation of wxComboBox. So here,
we look and if the selection is -1, and the value according to
(the base control!) is a value in the list, then we schedule a
programmatic wxComboBox.SetSelection() call to pick the appropriate
item in the list. (and then do the usual OnReturn bit.)
"""
## dbg('MaskedComboBox::OnReturn', indent=1)
## dbg('current value: "%s"' % self.GetValue(), 'current index:', self.GetSelection())
if self.GetSelection() == -1 and self.GetValue().lower().strip() in self._ctrl_constraints._compareChoices:
wx.CallAfter(self.SetSelection, self._ctrl_constraints._autoCompleteIndex)
event.m_keyCode = wx.WXK_TAB
event.Skip()
## dbg(indent=0)
class ComboBox( BaseMaskedComboBox, MaskedEditAccessorsMixin ):
"""
This extra level of inheritance allows us to add the generic set of
masked edit parameters only to this class while allowing other
classes to derive from the "base" masked combobox control, and provide
a smaller set of valid accessor functions.
"""
pass

View File

@ -1,5 +1,5 @@
#---------------------------------------------------------------------------- #----------------------------------------------------------------------------
# Name: wxPython.lib.maskedctrl.py # Name: wxPython.lib.masked.ctrl.py
# Author: Will Sadkin # Author: Will Sadkin
# Created: 09/24/2003 # Created: 09/24/2003
# Copyright: (c) 2003 by Will Sadkin # Copyright: (c) 2003 by Will Sadkin
@ -9,32 +9,32 @@
# 12/09/2003 - Jeff Grimmett (grimmtooth@softhome.net) # 12/09/2003 - Jeff Grimmett (grimmtooth@softhome.net)
# #
# o Updated for wx namespace (minor) # o Updated for wx namespace (minor)
# #
# 12/20/2003 - Jeff Grimmett (grimmtooth@softhome.net) # 12/20/2003 - Jeff Grimmett (grimmtooth@softhome.net)
# #
# o Removed wx prefix # o Removed wx prefix
# #
"""<html><body> """<html><body>
<P> <P>
<B>MaskedCtrl</B> is actually a factory function for several types of <B>masked.Ctrl</B> is actually a factory function for several types of
masked edit controls: masked edit controls:
<P> <P>
<UL> <UL>
<LI><b>MaskedTextCtrl</b> - standard masked edit text box</LI> <LI><b>masked.TextCtrl</b> - standard masked edit text box</LI>
<LI><b>MaskedComboBox</b> - adds combobox capabilities</LI> <LI><b>masked.ComboBox</b> - adds combobox capabilities</LI>
<LI><b>IpAddrCtrl</b> - adds logical input semantics for IP address entry</LI> <LI><b>masked.IpAddrCtrl</b> - adds logical input semantics for IP address entry</LI>
<LI><b>TimeCtrl</b> - special subclass handling lots of time formats as values</LI> <LI><b>masked.TimeCtrl</b> - special subclass handling lots of time formats as values</LI>
<LI><b>MaskedNumCtrl</b> - special subclass handling numeric values</LI> <LI><b>masked.NumCtrl</b> - special subclass handling numeric values</LI>
</UL> </UL>
<P> <P>
<B>MaskedCtrl</B> works by looking for a special <b><i>controlType</i></b> <B>masked.Ctrl</B> works by looking for a special <b><i>controlType</i></b>
parameter in the variable arguments of the control, to determine parameter in the variable arguments of the control, to determine
what kind of instance to return. what kind of instance to return.
controlType can be one of: controlType can be one of:
<PRE><FONT SIZE=-1> <PRE><FONT SIZE=-1>
controlTypes.MASKEDTEXT controlTypes.TEXT
controlTypes.MASKEDCOMBO controlTypes.COMBO
controlTypes.IPADDR controlTypes.IPADDR
controlTypes.TIME controlTypes.TIME
controlTypes.NUMBER controlTypes.NUMBER
@ -42,56 +42,56 @@ controlType can be one of:
These constants are also available individually, ie, you can These constants are also available individually, ie, you can
use either of the following: use either of the following:
<PRE><FONT SIZE=-1> <PRE><FONT SIZE=-1>
from wxPython.wx.lib.maskedctrl import MaskedCtrl, MASKEDCOMBO, MASKEDTEXT, NUMBER from wxPython.wx.lib.masked import Ctrl, COMBO, TEXT, NUMBER, TIME
from wxPython.wx.lib.maskedctrl import MaskedCtrl, controlTypes from wxPython.wx.lib.masked import Ctrl, controlTypes
</FONT></PRE> </FONT></PRE>
If not specified as a keyword argument, the default controlType is If not specified as a keyword argument, the default controlType is
controlTypes.MASKEDTEXT. controlTypes.TEXT.
<P> <P>
Each of the above classes has its own unique arguments, but MaskedCtrl Each of the above classes has its own unique arguments, but MaskedCtrl
provides a single "unified" interface for masked controls. MaskedTextCtrl, provides a single "unified" interface for masked controls. Masked.TextCtrl,
MaskedComboBox and IpAddrCtrl are all documented below; the others have masked.ComboBox and masked.IpAddrCtrl are all documented below; the others have
their own demo pages and interface descriptions. their own demo pages and interface descriptions.
</body></html> </body></html>
""" """
from wx.lib.maskededit import MaskedTextCtrl, MaskedComboBox, IpAddrCtrl from wx.lib.masked import TextCtrl, ComboBox, IpAddrCtrl
from wx.lib.maskednumctrl import MaskedNumCtrl from wx.lib.masked import NumCtrl
from wx.lib.timectrl import TimeCtrl from wx.lib.masked import TimeCtrl
# "type" enumeration for class instance factory function # "type" enumeration for class instance factory function
MASKEDTEXT = 0 TEXT = 0
MASKEDCOMBO = 1 COMBO = 1
IPADDR = 2 IPADDR = 2
TIME = 3 TIME = 3
NUMBER = 4 NUMBER = 4
# for ease of import # for ease of import
class controlTypes: class controlTypes:
MASKEDTEXT = MASKEDTEXT TEXT = TEXT
MASKEDCOMBO = MASKEDCOMBO COMBO = COMBO
IPADDR = IPADDR IPADDR = IPADDR
TIME = TIME TIME = TIME
NUMBER = NUMBER NUMBER = NUMBER
def MaskedCtrl( *args, **kwargs): def Ctrl( *args, **kwargs):
""" """
Actually a factory function providing a unifying Actually a factory function providing a unifying
interface for generating masked controls. interface for generating masked controls.
""" """
if not kwargs.has_key('controlType'): if not kwargs.has_key('controlType'):
controlType = MASKEDTEXT controlType = TEXT
else: else:
controlType = kwargs['controlType'] controlType = kwargs['controlType']
del kwargs['controlType'] del kwargs['controlType']
if controlType == MASKEDTEXT: if controlType == TEXT:
return MaskedTextCtrl(*args, **kwargs) return TextCtrl(*args, **kwargs)
elif controlType == MASKEDCOMBO: elif controlType == COMBO:
return MaskedComboBox(*args, **kwargs) return ComboBox(*args, **kwargs)
elif controlType == IPADDR: elif controlType == IPADDR:
return IpAddrCtrl(*args, **kwargs) return IpAddrCtrl(*args, **kwargs)
@ -100,7 +100,7 @@ def MaskedCtrl( *args, **kwargs):
return TimeCtrl(*args, **kwargs) return TimeCtrl(*args, **kwargs)
elif controlType == NUMBER: elif controlType == NUMBER:
return MaskedNumCtrl(*args, **kwargs) return NumCtrl(*args, **kwargs)
else: else:
raise AttributeError( raise AttributeError(

View File

@ -0,0 +1,187 @@
#----------------------------------------------------------------------------
# Name: masked.ipaddrctrl.py
# Authors: Will Sadkin
# Email: wsadkin@nameconnector.com
# Created: 02/11/2003
# Copyright: (c) 2003 by Will Sadkin, 2003
# RCS-ID: $Id$
# License: wxWidgets license
#----------------------------------------------------------------------------
# NOTE:
# Masked.IpAddrCtrl is a minor modification to masked.TextCtrl, that is
# specifically tailored for entering IP addresses. It allows for
# right-insert fields and provides an accessor to obtain the entered
# address with extra whitespace removed.
#
#----------------------------------------------------------------------------
import wx
from wx.lib.masked import BaseMaskedTextCtrl
# jmg 12/9/03 - when we cut ties with Py 2.2 and earlier, this would
# be a good place to implement the 2.3 logger class
from wx.tools.dbg import Logger
dbg = Logger()
##dbg(enable=0)
class IpAddrCtrlAccessorsMixin:
# Define IpAddrCtrl's list of attributes having their own
# Get/Set functions, exposing only those that make sense for
# an IP address control.
exposed_basectrl_params = (
'fields',
'retainFieldValidation',
'formatcodes',
'fillChar',
'defaultValue',
'description',
'useFixedWidthFont',
'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 IpAddrCtrl( BaseMaskedTextCtrl, IpAddrCtrlAccessorsMixin ):
"""
This class is a particular type of MaskedTextCtrl that accepts
and understands the semantics of IP addresses, reformats input
as you move from field to field, and accepts '.' as a navigation
character, so that typing an IP address can be done naturally.
"""
def __init__( self, parent, id=-1, value = '',
pos = wx.DefaultPosition,
size = wx.DefaultSize,
style = wx.TE_PROCESS_TAB,
validator = wx.DefaultValidator,
name = 'IpAddrCtrl',
setupEventHandling = True, ## setup event handling by default
**kwargs):
if not kwargs.has_key('mask'):
kwargs['mask'] = mask = "###.###.###.###"
if not kwargs.has_key('formatcodes'):
kwargs['formatcodes'] = 'F_Sr<'
if not kwargs.has_key('validRegex'):
kwargs['validRegex'] = "( \d| \d\d|(1\d\d|2[0-4]\d|25[0-5]))(\.( \d| \d\d|(1\d\d|2[0-4]\d|25[0-5]))){3}"
BaseMaskedTextCtrl.__init__(
self, parent, id=id, value = value,
pos=pos, size=size,
style = style,
validator = validator,
name = name,
setupEventHandling = setupEventHandling,
**kwargs)
# set up individual field parameters as well:
field_params = {}
field_params['validRegex'] = "( | \d| \d |\d | \d\d|\d\d |\d \d|(1\d\d|2[0-4]\d|25[0-5]))"
# require "valid" string; this prevents entry of any value > 255, but allows
# intermediate constructions; overall control validation requires well-formatted value.
field_params['formatcodes'] = 'V'
if field_params:
for i in self._field_indices:
self.SetFieldParameters(i, **field_params)
# This makes '.' act like tab:
self._AddNavKey('.', handler=self.OnDot)
self._AddNavKey('>', handler=self.OnDot) # for "shift-."
def OnDot(self, event):
## dbg('IpAddrCtrl::OnDot', indent=1)
pos = self._adjustPos(self._GetInsertionPoint(), event.GetKeyCode())
oldvalue = self.GetValue()
edit_start, edit_end, slice = self._FindFieldExtent(pos, getslice=True)
if not event.ShiftDown():
if pos > edit_start and pos < edit_end:
# clip data in field to the right of pos, if adjusting fields
# when not at delimeter; (assumption == they hit '.')
newvalue = oldvalue[:pos] + ' ' * (edit_end - pos) + oldvalue[edit_end:]
self._SetValue(newvalue)
self._SetInsertionPoint(pos)
## dbg(indent=0)
return self._OnChangeField(event)
def GetAddress(self):
value = BaseMaskedTextCtrl.GetValue(self)
return value.replace(' ','') # remove spaces from the value
def _OnCtrl_S(self, event):
## dbg("IpAddrCtrl::_OnCtrl_S")
if self._demo:
print "value:", self.GetAddress()
return False
def SetValue(self, value):
## dbg('IpAddrCtrl::SetValue(%s)' % str(value), indent=1)
if type(value) not in (types.StringType, types.UnicodeType):
## dbg(indent=0)
raise ValueError('%s must be a string', str(value))
bValid = True # assume True
parts = value.split('.')
if len(parts) != 4:
bValid = False
else:
for i in range(4):
part = parts[i]
if not 0 <= len(part) <= 3:
bValid = False
break
elif part.strip(): # non-empty part
try:
j = string.atoi(part)
if not 0 <= j <= 255:
bValid = False
break
else:
parts[i] = '%3d' % j
except:
bValid = False
break
else:
# allow empty sections for SetValue (will result in "invalid" value,
# but this may be useful for initializing the control:
parts[i] = ' ' # convert empty field to 3-char length
if not bValid:
## dbg(indent=0)
raise ValueError('value (%s) must be a string of form n.n.n.n where n is empty or in range 0-255' % str(value))
else:
## dbg('parts:', parts)
value = string.join(parts, '.')
BaseMaskedTextCtrl.SetValue(self, value)
## dbg(indent=0)

View File

@ -1,5 +1,5 @@
#---------------------------------------------------------------------------- #----------------------------------------------------------------------------
# Name: wxPython.lib.maskednumctrl.py # Name: wxPython.lib.masked.numctrl.py
# Author: Will Sadkin # Author: Will Sadkin
# Created: 09/06/2003 # Created: 09/06/2003
# Copyright: (c) 2003 by Will Sadkin # Copyright: (c) 2003 by Will Sadkin
@ -9,12 +9,12 @@
# NOTE: # NOTE:
# This was written to provide a numeric edit control for wxPython that # This was written to provide a numeric edit control for wxPython that
# does things like right-insert (like a calculator), and does grouping, etc. # does things like right-insert (like a calculator), and does grouping, etc.
# (ie. the features of MaskedTextCtrl), but allows Get/Set of numeric # (ie. the features of masked.TextCtrl), but allows Get/Set of numeric
# values, rather than text. # values, rather than text.
# #
# MaskedNumCtrl permits integer, and floating point values to be set # Masked.NumCtrl permits integer, and floating point values to be set
# retrieved or set via .GetValue() and .SetValue() (type chosen based on # retrieved or set via .GetValue() and .SetValue() (type chosen based on
# fraction width, and provides an EVT_MASKEDNUM() event function for trapping # fraction width, and provides an masked.EVT_NUM() event function for trapping
# changes to the control. # changes to the control.
# #
# It supports negative numbers as well as the naturals, and has the option # It supports negative numbers as well as the naturals, and has the option
@ -24,29 +24,29 @@
# Similarly, replacing the contents of the control with '-' will result in # Similarly, replacing the contents of the control with '-' will result in
# a selected (absolute) value of -1. # a selected (absolute) value of -1.
# #
# MaskedNumCtrl also supports range limits, with the option of either # masked.NumCtrl also supports range limits, with the option of either
# enforcing them or simply coloring the text of the control if the limits # enforcing them or simply coloring the text of the control if the limits
# are exceeded. # are exceeded.
# #
# MaskedNumCtrl is intended to support fixed-point numeric entry, and # masked.NumCtrl is intended to support fixed-point numeric entry, and
# is derived from BaseMaskedTextCtrl. 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. # of values to comply with a fixed-width entry mask.
#---------------------------------------------------------------------------- #----------------------------------------------------------------------------
# 12/09/2003 - Jeff Grimmett (grimmtooth@softhome.net) # 12/09/2003 - Jeff Grimmett (grimmtooth@softhome.net)
# #
# o Updated for wx namespace # o Updated for wx namespace
# #
# 12/20/2003 - Jeff Grimmett (grimmtooth@softhome.net) # 12/20/2003 - Jeff Grimmett (grimmtooth@softhome.net)
# #
# o wxMaskedEditMixin -> MaskedEditMixin # o wxMaskedEditMixin -> MaskedEditMixin
# o wxMaskedTextCtrl -> MaskedTextCtrl # o wxMaskedTextCtrl -> masked.TextCtrl
# o wxMaskedNumNumberUpdatedEvent -> MaskedNumNumberUpdatedEvent # o wxMaskedNumNumberUpdatedEvent -> masked.NumberUpdatedEvent
# o wxMaskedNumCtrl -> MaskedNumCtrl # o wxMaskedNumCtrl -> masked.NumCtrl
# #
"""<html><body> """<html><body>
<P> <P>
<B>MaskedNumCtrl:</B> <B>masked.NumCtrl:</B>
<UL> <UL>
<LI>allows you to get and set integer or floating point numbers as value,</LI> <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>provides bounds support and optional value limiting,</LI>
@ -62,14 +62,14 @@ fractional portion.
<P> <P>
Here's the API: Here's the API:
<DL><PRE> <DL><PRE>
<B>MaskedNumCtrl</B>( <B>masked.NumCtrl</B>(
parent, id = -1, parent, id = -1,
<B>value</B> = 0, <B>value</B> = 0,
pos = wx.DefaultPosition, pos = wx.DefaultPosition,
size = wx.DefaultSize, size = wx.DefaultSize,
style = 0, style = 0,
validator = wx.DefaultValidator, validator = wx.DefaultValidator,
name = "maskednumber", name = "masked.number",
<B>integerWidth</B> = 10, <B>integerWidth</B> = 10,
<B>fractionWidth</B> = 0, <B>fractionWidth</B> = 0,
<B>allowNone</B> = False, <B>allowNone</B> = False,
@ -87,7 +87,7 @@ Here's the API:
<B>emptyBackgroundColour</B> = "White", <B>emptyBackgroundColour</B> = "White",
<B>validBackgroundColour</B> = "White", <B>validBackgroundColour</B> = "White",
<B>invalidBackgroundColour</B> = "Yellow", <B>invalidBackgroundColour</B> = "Yellow",
<B>autoSize</B> = True <B>autoSize</B> = True
) )
</PRE> </PRE>
<UL> <UL>
@ -190,7 +190,7 @@ Here's the API:
</UL> </UL>
<BR> <BR>
<BR> <BR>
<DT><B>EVT_MASKEDNUM(win, id, func)</B> <DT><B>masked.EVT_NUM(win, id, func)</B>
<DD>Respond to a EVT_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 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 control's contents changes - whether this is due to user input or
@ -384,18 +384,18 @@ MAXINT = maxint # (constants should be in upper case)
MININT = -maxint-1 MININT = -maxint-1
from wx.tools.dbg import Logger from wx.tools.dbg import Logger
from wx.lib.maskededit import MaskedEditMixin, BaseMaskedTextCtrl, Field from wx.lib.masked import MaskedEditMixin, Field, BaseMaskedTextCtrl
dbg = Logger() dbg = Logger()
dbg(enable=0) ##dbg(enable=0)
#---------------------------------------------------------------------------- #----------------------------------------------------------------------------
wxEVT_COMMAND_MASKED_NUMBER_UPDATED = wx.NewEventType() wxEVT_COMMAND_MASKED_NUMBER_UPDATED = wx.NewEventType()
EVT_MASKEDNUM = wx.PyEventBinder(wxEVT_COMMAND_MASKED_NUMBER_UPDATED, 1) EVT_NUM = wx.PyEventBinder(wxEVT_COMMAND_MASKED_NUMBER_UPDATED, 1)
#---------------------------------------------------------------------------- #----------------------------------------------------------------------------
class MaskedNumNumberUpdatedEvent(wx.PyCommandEvent): class NumberUpdatedEvent(wx.PyCommandEvent):
def __init__(self, id, value = 0, object=None): def __init__(self, id, value = 0, object=None):
wx.PyCommandEvent.__init__(self, wxEVT_COMMAND_MASKED_NUMBER_UPDATED, id) wx.PyCommandEvent.__init__(self, wxEVT_COMMAND_MASKED_NUMBER_UPDATED, id)
@ -409,8 +409,8 @@ class MaskedNumNumberUpdatedEvent(wx.PyCommandEvent):
#---------------------------------------------------------------------------- #----------------------------------------------------------------------------
class MaskedNumCtrlAccessorsMixin: class NumCtrlAccessorsMixin:
# Define wxMaskedNumCtrl's list of attributes having their own # Define masked.NumCtrl's list of attributes having their own
# Get/Set functions, ignoring those that make no sense for # Get/Set functions, ignoring those that make no sense for
# an numeric control. # an numeric control.
exposed_basectrl_params = ( exposed_basectrl_params = (
@ -447,8 +447,8 @@ class MaskedNumCtrlAccessorsMixin:
#---------------------------------------------------------------------------- #----------------------------------------------------------------------------
class MaskedNumCtrl(BaseMaskedTextCtrl, MaskedNumCtrlAccessorsMixin): class NumCtrl(BaseMaskedTextCtrl, NumCtrlAccessorsMixin):
valid_ctrl_params = { valid_ctrl_params = {
@ -470,7 +470,7 @@ class MaskedNumCtrl(BaseMaskedTextCtrl, MaskedNumCtrlAccessorsMixin):
'validBackgroundColour': "White", 'validBackgroundColour': "White",
'invalidBackgroundColour': "Yellow", '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 'autoSize': True, # by default, set the width of the control based on the mask
} }
@ -478,31 +478,32 @@ class MaskedNumCtrl(BaseMaskedTextCtrl, MaskedNumCtrlAccessorsMixin):
self, parent, id=-1, value = 0, self, parent, id=-1, value = 0,
pos = wx.DefaultPosition, size = wx.DefaultSize, pos = wx.DefaultPosition, size = wx.DefaultSize,
style = wx.TE_PROCESS_TAB, validator = wx.DefaultValidator, style = wx.TE_PROCESS_TAB, validator = wx.DefaultValidator,
name = "maskednum", name = "masked.num",
**kwargs ): **kwargs ):
dbg('MaskedNumCtrl::__init__', indent=1) ## dbg('masked.NumCtrl::__init__', indent=1)
# Set defaults for control: # Set defaults for control:
dbg('setting defaults:') ## dbg('setting defaults:')
for key, param_value in MaskedNumCtrl.valid_ctrl_params.items(): for key, param_value in NumCtrl.valid_ctrl_params.items():
# This is done this way to make setattr behave consistently with # This is done this way to make setattr behave consistently with
# "private attribute" name mangling # "private attribute" name mangling
setattr(self, '_' + key, copy.copy(param_value)) setattr(self, '_' + key, copy.copy(param_value))
# Assign defaults for all attributes: # Assign defaults for all attributes:
init_args = copy.deepcopy(MaskedNumCtrl.valid_ctrl_params) init_args = copy.deepcopy(NumCtrl.valid_ctrl_params)
dbg('kwargs:', kwargs) ## dbg('kwargs:', kwargs)
for key, param_value in kwargs.items(): for key, param_value in kwargs.items():
key = key.replace('Color', 'Colour') key = key.replace('Color', 'Colour')
if key not in MaskedNumCtrl.valid_ctrl_params.keys(): if key not in NumCtrl.valid_ctrl_params.keys():
raise AttributeError('invalid keyword argument "%s"' % key) raise AttributeError('invalid keyword argument "%s"' % key)
else: else:
init_args[key] = param_value init_args[key] = param_value
dbg('init_args:', indent=1) ## dbg('init_args:', indent=1)
for key, param_value in init_args.items(): for key, param_value in init_args.items():
dbg('%s:' % key, param_value) ## dbg('%s:' % key, param_value)
dbg(indent=0) pass
## dbg(indent=0)
# Process initial fields for the control, as part of construction: # Process initial fields for the control, as part of construction:
if type(init_args['integerWidth']) != types.IntType: if type(init_args['integerWidth']) != types.IntType:
@ -521,7 +522,7 @@ class MaskedNumCtrl(BaseMaskedTextCtrl, MaskedNumCtrlAccessorsMixin):
if self._fractionWidth: if self._fractionWidth:
fracmask = '.' + '#{%d}' % self._fractionWidth fracmask = '.' + '#{%d}' % self._fractionWidth
dbg('fracmask:', fracmask) ## dbg('fracmask:', fracmask)
fields[1] = Field(defaultValue='0'*self._fractionWidth) fields[1] = Field(defaultValue='0'*self._fractionWidth)
else: else:
fracmask = '' fracmask = ''
@ -537,7 +538,7 @@ class MaskedNumCtrl(BaseMaskedTextCtrl, MaskedNumCtrlAccessorsMixin):
else: else:
emptyInvalid = True emptyInvalid = True
fields[0] = Field(formatcodes='r<>', emptyInvalid=emptyInvalid) fields[0] = Field(formatcodes='r<>', emptyInvalid=emptyInvalid)
dbg('intmask:', intmask) ## dbg('intmask:', intmask)
# don't bother to reprocess these arguments: # don't bother to reprocess these arguments:
del init_args['integerWidth'] del init_args['integerWidth']
@ -585,14 +586,14 @@ class MaskedNumCtrl(BaseMaskedTextCtrl, MaskedNumCtrlAccessorsMixin):
# Ensure proper coloring: # Ensure proper coloring:
self.Refresh() self.Refresh()
dbg('finished MaskedNumCtrl::__init__', indent=0) ## dbg('finished NumCtrl::__init__', indent=0)
def SetParameters(self, **kwargs): def SetParameters(self, **kwargs):
""" """
This routine is used to initialize and reconfigure the control: This routine is used to initialize and reconfigure the control:
""" """
dbg('MaskedNumCtrl::SetParameters', indent=1) ## dbg('NumCtrl::SetParameters', indent=1)
maskededit_kwargs = {} maskededit_kwargs = {}
reset_fraction_width = False reset_fraction_width = False
@ -620,14 +621,14 @@ class MaskedNumCtrl(BaseMaskedTextCtrl, MaskedNumCtrlAccessorsMixin):
else: else:
emptyInvalid = True emptyInvalid = True
fracmask = '' fracmask = ''
dbg('fracmask:', fracmask) ## dbg('fracmask:', fracmask)
if kwargs.has_key('integerWidth'): if kwargs.has_key('integerWidth'):
if type(kwargs['integerWidth']) != types.IntType: if type(kwargs['integerWidth']) != types.IntType:
dbg(indent=0) ## dbg(indent=0)
raise AttributeError('invalid integerWidth (%s) specified; expected integer' % repr(kwargs['integerWidth'])) raise AttributeError('invalid integerWidth (%s) specified; expected integer' % repr(kwargs['integerWidth']))
elif kwargs['integerWidth'] < 0: elif kwargs['integerWidth'] < 0:
dbg(indent=0) ## dbg(indent=0)
raise AttributeError('invalid integerWidth (%s) specified; must be > 0' % repr(kwargs['integerWidth'])) raise AttributeError('invalid integerWidth (%s) specified; must be > 0' % repr(kwargs['integerWidth']))
else: else:
self._integerWidth = kwargs['integerWidth'] self._integerWidth = kwargs['integerWidth']
@ -641,7 +642,7 @@ class MaskedNumCtrl(BaseMaskedTextCtrl, MaskedNumCtrlAccessorsMixin):
self._groupSpace = 0 self._groupSpace = 0
intmask = '#{%d}' % (self._integerWidth + self._groupSpace) intmask = '#{%d}' % (self._integerWidth + self._groupSpace)
dbg('intmask:', intmask) ## dbg('intmask:', intmask)
fields[0] = Field(formatcodes='r<>', emptyInvalid=emptyInvalid) fields[0] = Field(formatcodes='r<>', emptyInvalid=emptyInvalid)
maskededit_kwargs['fields'] = fields maskededit_kwargs['fields'] = fields
@ -655,17 +656,17 @@ class MaskedNumCtrl(BaseMaskedTextCtrl, MaskedNumCtrlAccessorsMixin):
if kwargs.has_key('groupChar'): if kwargs.has_key('groupChar'):
old_groupchar = self._groupChar # save so we can reformat properly old_groupchar = self._groupChar # save so we can reformat properly
dbg("old_groupchar: '%s'" % old_groupchar) ## dbg("old_groupchar: '%s'" % old_groupchar)
maskededit_kwargs['groupChar'] = kwargs['groupChar'] maskededit_kwargs['groupChar'] = kwargs['groupChar']
if kwargs.has_key('decimalChar'): if kwargs.has_key('decimalChar'):
old_decimalchar = self._decimalChar old_decimalchar = self._decimalChar
dbg("old_decimalchar: '%s'" % old_decimalchar) ## dbg("old_decimalchar: '%s'" % old_decimalchar)
maskededit_kwargs['decimalChar'] = kwargs['decimalChar'] maskededit_kwargs['decimalChar'] = kwargs['decimalChar']
# for all other parameters, assign keyword args as appropriate: # for all other parameters, assign keyword args as appropriate:
for key, param_value in kwargs.items(): for key, param_value in kwargs.items():
key = key.replace('Color', 'Colour') key = key.replace('Color', 'Colour')
if key not in MaskedNumCtrl.valid_ctrl_params.keys(): if key not in NumCtrl.valid_ctrl_params.keys():
raise AttributeError('invalid keyword argument "%s"' % key) raise AttributeError('invalid keyword argument "%s"' % key)
elif key not in MaskedEditMixin.valid_ctrl_params.keys(): elif key not in MaskedEditMixin.valid_ctrl_params.keys():
setattr(self, '_' + key, param_value) setattr(self, '_' + key, param_value)
@ -673,10 +674,10 @@ class MaskedNumCtrl(BaseMaskedTextCtrl, MaskedNumCtrlAccessorsMixin):
raise AttributeError('invalid keyword argument "%s"' % key) raise AttributeError('invalid keyword argument "%s"' % key)
else: else:
maskededit_kwargs[key] = param_value maskededit_kwargs[key] = param_value
dbg('kwargs:', kwargs) ## dbg('kwargs:', kwargs)
# reprocess existing format codes to ensure proper resulting format: # reprocess existing format codes to ensure proper resulting format:
formatcodes = self.GetCtrlParameter('formatcodes') formatcodes = self.GetCtrlParameter('formatcodes')
if kwargs.has_key('allowNegative'): if kwargs.has_key('allowNegative'):
if kwargs['allowNegative'] and '-' not in formatcodes: if kwargs['allowNegative'] and '-' not in formatcodes:
formatcodes += '-' formatcodes += '-'
@ -695,7 +696,7 @@ class MaskedNumCtrl(BaseMaskedTextCtrl, MaskedNumCtrlAccessorsMixin):
if kwargs.has_key('selectOnEntry'): if kwargs.has_key('selectOnEntry'):
self._selectOnEntry = kwargs['selectOnEntry'] self._selectOnEntry = kwargs['selectOnEntry']
dbg("kwargs['selectOnEntry']?", kwargs['selectOnEntry'], "'S' in formatcodes?", 'S' in formatcodes) ## dbg("kwargs['selectOnEntry']?", kwargs['selectOnEntry'], "'S' in formatcodes?", 'S' in formatcodes)
if kwargs['selectOnEntry'] and 'S' not in formatcodes: if kwargs['selectOnEntry'] and 'S' not in formatcodes:
formatcodes += 'S' formatcodes += 'S'
maskededit_kwargs['formatcodes'] = formatcodes maskededit_kwargs['formatcodes'] = formatcodes
@ -728,7 +729,7 @@ class MaskedNumCtrl(BaseMaskedTextCtrl, MaskedNumCtrlAccessorsMixin):
maskededit_kwargs['validRequired'] = False maskededit_kwargs['validRequired'] = False
self._limited = kwargs['limited'] self._limited = kwargs['limited']
dbg('maskededit_kwargs:', maskededit_kwargs) ## dbg('maskededit_kwargs:', maskededit_kwargs)
if maskededit_kwargs.keys(): if maskededit_kwargs.keys():
self.SetCtrlParameters(**maskededit_kwargs) self.SetCtrlParameters(**maskededit_kwargs)
@ -756,17 +757,18 @@ class MaskedNumCtrl(BaseMaskedTextCtrl, MaskedNumCtrlAccessorsMixin):
if( self._max is None if( self._max is None
or min is None or min is None
or (self._max is not None and self._max >= min) ): or (self._max is not None and self._max >= min) ):
dbg('examining min') ## dbg('examining min')
if min is not None: if min is not None:
try: try:
textmin = self._toGUI(min, apply_limits = False) textmin = self._toGUI(min, apply_limits = False)
except ValueError: except ValueError:
dbg('min will not fit into control; ignoring', indent=0) ## dbg('min will not fit into control; ignoring', indent=0)
raise raise
dbg('accepted min') ## dbg('accepted min')
self._min = min self._min = min
else: else:
dbg('ignoring min') ## dbg('ignoring min')
pass
if kwargs.has_key('max'): if kwargs.has_key('max'):
@ -774,24 +776,25 @@ class MaskedNumCtrl(BaseMaskedTextCtrl, MaskedNumCtrlAccessorsMixin):
if( self._min is None if( self._min is None
or max is None or max is None
or (self._min is not None and self._min <= max) ): or (self._min is not None and self._min <= max) ):
dbg('examining max') ## dbg('examining max')
if max is not None: if max is not None:
try: try:
textmax = self._toGUI(max, apply_limits = False) textmax = self._toGUI(max, apply_limits = False)
except ValueError: except ValueError:
dbg('max will not fit into control; ignoring', indent=0) ## dbg('max will not fit into control; ignoring', indent=0)
raise raise
dbg('accepted max') ## dbg('accepted max')
self._max = max self._max = max
else: else:
dbg('ignoring max') ## dbg('ignoring max')
pass
if kwargs.has_key('allowNegative'): if kwargs.has_key('allowNegative'):
self._allowNegative = kwargs['allowNegative'] self._allowNegative = kwargs['allowNegative']
# Ensure current value of control obeys any new restrictions imposed: # Ensure current value of control obeys any new restrictions imposed:
text = self._GetValue() text = self._GetValue()
dbg('text value: "%s"' % text) ## dbg('text value: "%s"' % text)
if kwargs.has_key('groupChar') and text.find(old_groupchar) != -1: if kwargs.has_key('groupChar') and text.find(old_groupchar) != -1:
text = text.replace(old_groupchar, self._groupChar) text = text.replace(old_groupchar, self._groupChar)
if kwargs.has_key('decimalChar') and text.find(old_decimalchar) != -1: if kwargs.has_key('decimalChar') and text.find(old_decimalchar) != -1:
@ -801,10 +804,10 @@ class MaskedNumCtrl(BaseMaskedTextCtrl, MaskedNumCtrlAccessorsMixin):
value = self.GetValue() value = self.GetValue()
dbg('self._allowNegative?', self._allowNegative) ## dbg('self._allowNegative?', self._allowNegative)
if not self._allowNegative and self._isNeg: if not self._allowNegative and self._isNeg:
value = abs(value) value = abs(value)
dbg('abs(value):', value) ## dbg('abs(value):', value)
self._isNeg = False self._isNeg = False
elif not self._allowNone and BaseMaskedTextCtrl.GetValue(self) == '': elif not self._allowNone and BaseMaskedTextCtrl.GetValue(self) == '':
@ -815,19 +818,19 @@ class MaskedNumCtrl(BaseMaskedTextCtrl, MaskedNumCtrlAccessorsMixin):
sel_start, sel_to = self.GetSelection() sel_start, sel_to = self.GetSelection()
if self.IsLimited() and self._min is not None and value < self._min: if self.IsLimited() and self._min is not None and value < self._min:
dbg('Set to min value:', self._min) ## dbg('Set to min value:', self._min)
self._SetValue(self._toGUI(self._min)) self._SetValue(self._toGUI(self._min))
elif self.IsLimited() and self._max is not None and value > self._max: elif self.IsLimited() and self._max is not None and value > self._max:
dbg('Setting to max value:', self._max) ## dbg('Setting to max value:', self._max)
self._SetValue(self._toGUI(self._max)) self._SetValue(self._toGUI(self._max))
else: else:
# reformat current value as appropriate to possibly new conditions # reformat current value as appropriate to possibly new conditions
dbg('Reformatting value:', value) ## dbg('Reformatting value:', value)
sel_start, sel_to = self.GetSelection() sel_start, sel_to = self.GetSelection()
self._SetValue(self._toGUI(value)) self._SetValue(self._toGUI(value))
self.Refresh() # recolor as appropriate self.Refresh() # recolor as appropriate
dbg('finished MaskedNumCtrl::SetParameters', indent=0) ## dbg('finished NumCtrl::SetParameters', indent=0)
@ -859,21 +862,21 @@ class MaskedNumCtrl(BaseMaskedTextCtrl, MaskedNumCtrlAccessorsMixin):
return string.atof(fracstring) return string.atof(fracstring)
def _OnChangeSign(self, event): def _OnChangeSign(self, event):
dbg('MaskedNumCtrl::_OnChangeSign', indent=1) ## dbg('NumCtrl::_OnChangeSign', indent=1)
self._typedSign = True self._typedSign = True
MaskedEditMixin._OnChangeSign(self, event) MaskedEditMixin._OnChangeSign(self, event)
dbg(indent=0) ## dbg(indent=0)
def _disallowValue(self): def _disallowValue(self):
dbg('MaskedNumCtrl::_disallowValue') ## dbg('NumCtrl::_disallowValue')
# limited and -1 is out of bounds # limited and -1 is out of bounds
if self._typedSign: if self._typedSign:
self._isNeg = False self._isNeg = False
if not wx.Validator_IsSilent(): if not wx.Validator_IsSilent():
wx.Bell() wx.Bell()
sel_start, sel_to = self._GetSelection() sel_start, sel_to = self._GetSelection()
dbg('queuing reselection of (%d, %d)' % (sel_start, sel_to)) ## dbg('queuing reselection of (%d, %d)' % (sel_start, sel_to))
wx.CallAfter(self.SetInsertionPoint, sel_start) # preserve current selection/position wx.CallAfter(self.SetInsertionPoint, sel_start) # preserve current selection/position
wx.CallAfter(self.SetSelection, sel_start, sel_to) wx.CallAfter(self.SetSelection, sel_start, sel_to)
@ -886,19 +889,19 @@ class MaskedNumCtrl(BaseMaskedTextCtrl, MaskedNumCtrlAccessorsMixin):
by the user. by the user.
""" """
dbg('MaskedNumCtrl::_SetValue("%s")' % value, indent=1) ## dbg('NumCtrl::_SetValue("%s")' % value, indent=1)
if( (self._fractionWidth and value.find(self._decimalChar) == -1) or if( (self._fractionWidth and value.find(self._decimalChar) == -1) or
(self._fractionWidth == 0 and value.find(self._decimalChar) != -1) ) : (self._fractionWidth == 0 and value.find(self._decimalChar) != -1) ) :
value = self._toGUI(value) value = self._toGUI(value)
numvalue = self._GetNumValue(value) numvalue = self._GetNumValue(value)
dbg('cleansed value: "%s"' % numvalue) ## dbg('cleansed value: "%s"' % numvalue)
replacement = None replacement = None
if numvalue == "": if numvalue == "":
if self._allowNone: if self._allowNone:
dbg('calling base BaseMaskedTextCtrl._SetValue(self, "%s")' % value) ## dbg('calling base BaseMaskedTextCtrl._SetValue(self, "%s")' % value)
BaseMaskedTextCtrl._SetValue(self, value) BaseMaskedTextCtrl._SetValue(self, value)
self.Refresh() self.Refresh()
return return
@ -906,54 +909,54 @@ class MaskedNumCtrl(BaseMaskedTextCtrl, MaskedNumCtrlAccessorsMixin):
replacement = self._min replacement = self._min
else: else:
replacement = 0 replacement = 0
dbg('empty value; setting replacement:', replacement) ## dbg('empty value; setting replacement:', replacement)
if replacement is None: if replacement is None:
# Go get the integer portion about to be set and verify its validity # Go get the integer portion about to be set and verify its validity
intstart, intend = self._fields[0]._extent intstart, intend = self._fields[0]._extent
dbg('intstart, intend:', intstart, intend) ## dbg('intstart, intend:', intstart, intend)
dbg('raw integer:"%s"' % value[intstart:intend]) ## dbg('raw integer:"%s"' % value[intstart:intend])
int = self._GetNumValue(value[intstart:intend]) int = self._GetNumValue(value[intstart:intend])
numval = self._fromGUI(value) numval = self._fromGUI(value)
dbg('integer: "%s"' % int) ## dbg('integer: "%s"' % int)
try: try:
fracval = self.GetFraction(value) fracval = self.GetFraction(value)
except ValueError, e: except ValueError, e:
dbg('Exception:', e, 'must be out of bounds; disallow value') ## dbg('Exception:', e, 'must be out of bounds; disallow value')
self._disallowValue() self._disallowValue()
dbg(indent=0) ## dbg(indent=0)
return return
if fracval == 0.0: if fracval == 0.0:
dbg('self._isNeg?', self._isNeg) ## dbg('self._isNeg?', self._isNeg)
if int == '-' and self._oldvalue < 0 and not self._typedSign: if int == '-' and self._oldvalue < 0 and not self._typedSign:
dbg('just a negative sign; old value < 0; setting replacement of 0') ## dbg('just a negative sign; old value < 0; setting replacement of 0')
replacement = 0 replacement = 0
self._isNeg = False self._isNeg = False
elif int[:2] == '-0' and self._fractionWidth == 0: elif int[:2] == '-0' and self._fractionWidth == 0:
if self._oldvalue < 0: if self._oldvalue < 0:
dbg('-0; setting replacement of 0') ## dbg('-0; setting replacement of 0')
replacement = 0 replacement = 0
self._isNeg = False self._isNeg = False
elif not self._limited or (self._min < -1 and self._max >= -1): elif not self._limited or (self._min < -1 and self._max >= -1):
dbg('-0; setting replacement of -1') ## dbg('-0; setting replacement of -1')
replacement = -1 replacement = -1
self._isNeg = True self._isNeg = True
else: else:
# limited and -1 is out of bounds # limited and -1 is out of bounds
self._disallowValue() self._disallowValue()
dbg(indent=0) ## dbg(indent=0)
return return
elif int == '-' and (self._oldvalue >= 0 or self._typedSign) and self._fractionWidth == 0: elif int == '-' and (self._oldvalue >= 0 or self._typedSign) and self._fractionWidth == 0:
if not self._limited or (self._min < -1 and self._max >= -1): if not self._limited or (self._min < -1 and self._max >= -1):
dbg('just a negative sign; setting replacement of -1') ## dbg('just a negative sign; setting replacement of -1')
replacement = -1 replacement = -1
else: else:
# limited and -1 is out of bounds # limited and -1 is out of bounds
self._disallowValue() self._disallowValue()
dbg(indent=0) ## dbg(indent=0)
return return
elif( self._typedSign elif( self._typedSign
@ -963,7 +966,7 @@ class MaskedNumCtrl(BaseMaskedTextCtrl, MaskedNumCtrlAccessorsMixin):
# changed sign resulting in value that's now out-of-bounds; # changed sign resulting in value that's now out-of-bounds;
# disallow # disallow
self._disallowValue() self._disallowValue()
dbg(indent=0) ## dbg(indent=0)
return return
if replacement is None: if replacement is None:
@ -974,37 +977,37 @@ class MaskedNumCtrl(BaseMaskedTextCtrl, MaskedNumCtrlAccessorsMixin):
# integer requested is not legal. This can happen if the user # integer requested is not legal. This can happen if the user
# is attempting to insert a digit in the middle of the control # is attempting to insert a digit in the middle of the control
# resulting in something like " 3 45". Disallow such actions: # resulting in something like " 3 45". Disallow such actions:
dbg('>>>>>>>>>>>>>>>> "%s" does not convert to a long!' % int) ## dbg('>>>>>>>>>>>>>>>> "%s" does not convert to a long!' % int)
if not wx.Validator_IsSilent(): if not wx.Validator_IsSilent():
wx.Bell() wx.Bell()
sel_start, sel_to = self._GetSelection() sel_start, sel_to = self._GetSelection()
dbg('queuing reselection of (%d, %d)' % (sel_start, sel_to)) ## dbg('queuing reselection of (%d, %d)' % (sel_start, sel_to))
wx.CallAfter(self.SetInsertionPoint, sel_start) # preserve current selection/position wx.CallAfter(self.SetInsertionPoint, sel_start) # preserve current selection/position
wx.CallAfter(self.SetSelection, sel_start, sel_to) wx.CallAfter(self.SetSelection, sel_start, sel_to)
dbg(indent=0) ## dbg(indent=0)
return return
if int[0] == '0' and len(int) > 1: if int[0] == '0' and len(int) > 1:
dbg('numvalue: "%s"' % numvalue.replace(' ', '')) ## dbg('numvalue: "%s"' % numvalue.replace(' ', ''))
if self._fractionWidth: if self._fractionWidth:
value = self._toGUI(string.atof(numvalue)) value = self._toGUI(string.atof(numvalue))
else: else:
value = self._toGUI(string.atol(numvalue)) value = self._toGUI(string.atol(numvalue))
dbg('modified value: "%s"' % value) ## dbg('modified value: "%s"' % value)
self._typedSign = False # reset state var self._typedSign = False # reset state var
if replacement is not None: if replacement is not None:
# Value presented wasn't a legal number, but control should do something # Value presented wasn't a legal number, but control should do something
# reasonable instead: # reasonable instead:
dbg('setting replacement value:', replacement) ## dbg('setting replacement value:', replacement)
self._SetValue(self._toGUI(replacement)) self._SetValue(self._toGUI(replacement))
sel_start = BaseMaskedTextCtrl.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))) sel_to = sel_start + len(str(abs(replacement)))
dbg('queuing selection of (%d, %d)' %(sel_start, sel_to)) ## dbg('queuing selection of (%d, %d)' %(sel_start, sel_to))
wx.CallAfter(self.SetInsertionPoint, sel_start) wx.CallAfter(self.SetInsertionPoint, sel_start)
wx.CallAfter(self.SetSelection, sel_start, sel_to) wx.CallAfter(self.SetSelection, sel_start, sel_to)
dbg(indent=0) ## dbg(indent=0)
return return
# Otherwise, apply appropriate formatting to value: # Otherwise, apply appropriate formatting to value:
@ -1016,35 +1019,35 @@ class MaskedNumCtrl(BaseMaskedTextCtrl, MaskedNumCtrlAccessorsMixin):
else: else:
self._isNeg = False self._isNeg = False
dbg('value:"%s"' % value, 'self._useParens:', self._useParens) ## dbg('value:"%s"' % value, 'self._useParens:', self._useParens)
if self._fractionWidth: if self._fractionWidth:
adjvalue = self._adjustFloat(self._GetNumValue(value).replace('.',self._decimalChar)) adjvalue = self._adjustFloat(self._GetNumValue(value).replace('.',self._decimalChar))
else: else:
adjvalue = self._adjustInt(self._GetNumValue(value)) adjvalue = self._adjustInt(self._GetNumValue(value))
dbg('adjusted value: "%s"' % adjvalue) ## dbg('adjusted value: "%s"' % adjvalue)
sel_start, sel_to = self._GetSelection() # record current insertion point sel_start, sel_to = self._GetSelection() # record current insertion point
dbg('calling BaseMaskedTextCtrl._SetValue(self, "%s")' % adjvalue) ## dbg('calling BaseMaskedTextCtrl._SetValue(self, "%s")' % adjvalue)
BaseMaskedTextCtrl._SetValue(self, adjvalue) BaseMaskedTextCtrl._SetValue(self, adjvalue)
# After all actions so far scheduled, check that resulting cursor # After all actions so far scheduled, check that resulting cursor
# position is appropriate, and move if not: # position is appropriate, and move if not:
wx.CallAfter(self._CheckInsertionPoint) wx.CallAfter(self._CheckInsertionPoint)
dbg('finished MaskedNumCtrl::_SetValue', indent=0) ## dbg('finished NumCtrl::_SetValue', indent=0)
def _CheckInsertionPoint(self): def _CheckInsertionPoint(self):
# If current insertion point is before the end of the integer and # If current insertion point is before the end of the integer and
# its before the 1st digit, place it just after the sign position: # its before the 1st digit, place it just after the sign position:
dbg('MaskedNumCtrl::CheckInsertionPoint', indent=1) ## dbg('NumCtrl::CheckInsertionPoint', indent=1)
sel_start, sel_to = self._GetSelection() sel_start, sel_to = self._GetSelection()
text = self._GetValue() text = self._GetValue()
if sel_to < self._fields[0]._extent[1] and text[sel_to] in (' ', '-', '('): if sel_to < self._fields[0]._extent[1] and text[sel_to] in (' ', '-', '('):
text, signpos, right_signpos = self._getSignedValue() text, signpos, right_signpos = self._getSignedValue()
dbg('setting selection(%d, %d)' % (signpos+1, signpos+1)) ## dbg('setting selection(%d, %d)' % (signpos+1, signpos+1))
self.SetInsertionPoint(signpos+1) self.SetInsertionPoint(signpos+1)
self.SetSelection(signpos+1, signpos+1) self.SetSelection(signpos+1, signpos+1)
dbg(indent=0) ## dbg(indent=0)
def _OnErase( self, event ): def _OnErase( self, event ):
@ -1053,7 +1056,7 @@ class MaskedNumCtrl(BaseMaskedTextCtrl, MaskedNumCtrlAccessorsMixin):
grouping characters auto selects the digit before or after the grouping characters auto selects the digit before or after the
grouping character, so that the erasure does the right thing. grouping character, so that the erasure does the right thing.
""" """
dbg('MaskedNumCtrl::_OnErase', indent=1) ## dbg('NumCtrl::_OnErase', indent=1)
#if grouping digits, make sure deletes next to group char always #if grouping digits, make sure deletes next to group char always
# delete next digit to appropriate side: # delete next digit to appropriate side:
@ -1086,21 +1089,21 @@ class MaskedNumCtrl(BaseMaskedTextCtrl, MaskedNumCtrlAccessorsMixin):
self.SetSelection(sel_start, sel_to+1) self.SetSelection(sel_start, sel_to+1)
BaseMaskedTextCtrl._OnErase(self, event) BaseMaskedTextCtrl._OnErase(self, event)
dbg(indent=0) ## dbg(indent=0)
def OnTextChange( self, event ): def OnTextChange( self, event ):
""" """
Handles an event indicating that the text control's value Handles an event indicating that the text control's value
has changed, and issue EVT_MaskedNum event. has changed, and issue EVT_NUM event.
NOTE: using wxTextCtrl.SetValue() to change the control's NOTE: using wxTextCtrl.SetValue() to change the control's
contents from within a EVT_CHAR handler can cause double contents from within a EVT_CHAR handler can cause double
text events. So we check for actual changes to the text text events. So we check for actual changes to the text
before passing the events on. before passing the events on.
""" """
dbg('MaskedNumCtrl::OnTextChange', indent=1) ## dbg('NumCtrl::OnTextChange', indent=1)
if not BaseMaskedTextCtrl._OnTextChange(self, event): if not BaseMaskedTextCtrl._OnTextChange(self, event):
dbg(indent=0) ## dbg(indent=0)
return return
# else... legal value # else... legal value
@ -1109,14 +1112,14 @@ class MaskedNumCtrl(BaseMaskedTextCtrl, MaskedNumCtrlAccessorsMixin):
if value != self._oldvalue: if value != self._oldvalue:
try: try:
self.GetEventHandler().ProcessEvent( self.GetEventHandler().ProcessEvent(
MaskedNumNumberUpdatedEvent( self.GetId(), self.GetValue(), self ) ) NumberUpdatedEvent( self.GetId(), self.GetValue(), self ) )
except ValueError: except ValueError:
dbg(indent=0) ## dbg(indent=0)
return return
# let normal processing of the text continue # let normal processing of the text continue
event.Skip() event.Skip()
self._oldvalue = value # record for next event self._oldvalue = value # record for next event
dbg(indent=0) ## dbg(indent=0)
def _GetValue(self): def _GetValue(self):
""" """
@ -1172,7 +1175,7 @@ class MaskedNumCtrl(BaseMaskedTextCtrl, MaskedNumCtrlAccessorsMixin):
If min > the max value allowed by the width of the control, If min > the max value allowed by the width of the control,
the function will return False, and the min will not be set. the function will return False, and the min will not be set.
""" """
dbg('MaskedNumCtrl::SetMin(%s)' % repr(min), indent=1) ## dbg('NumCtrl::SetMin(%s)' % repr(min), indent=1)
if( self._max is None if( self._max is None
or min is None or min is None
or (self._max is not None and self._max >= min) ): or (self._max is not None and self._max >= min) ):
@ -1183,7 +1186,7 @@ class MaskedNumCtrl(BaseMaskedTextCtrl, MaskedNumCtrlAccessorsMixin):
bRet = False bRet = False
else: else:
bRet = False bRet = False
dbg(indent=0) ## dbg(indent=0)
return bRet return bRet
def GetMin(self): def GetMin(self):
@ -1286,14 +1289,14 @@ class MaskedNumCtrl(BaseMaskedTextCtrl, MaskedNumCtrlAccessorsMixin):
also be called with a value to see if that value would fall within also be called with a value to see if that value would fall within
the current bounds of the given control. the current bounds of the given control.
""" """
dbg('IsInBounds(%s)' % repr(value), indent=1) ## dbg('IsInBounds(%s)' % repr(value), indent=1)
if value is None: if value is None:
value = self.GetValue() value = self.GetValue()
else: else:
try: try:
value = self._GetNumValue(self._toGUI(value)) value = self._GetNumValue(self._toGUI(value))
except ValueError, e: except ValueError, e:
dbg('error getting NumValue(self._toGUI(value)):', e, indent=0) ## dbg('error getting NumValue(self._toGUI(value)):', e, indent=0)
return False return False
if value.strip() == '': if value.strip() == '':
value = None value = None
@ -1309,10 +1312,10 @@ class MaskedNumCtrl(BaseMaskedTextCtrl, MaskedNumCtrlAccessorsMixin):
# if bounds set, and value is None, return False # if bounds set, and value is None, return False
if value == None and (min is not None or max is not None): if value == None and (min is not None or max is not None):
dbg('finished IsInBounds', indent=0) ## dbg('finished IsInBounds', indent=0)
return 0 return 0
else: else:
dbg('finished IsInBounds', indent=0) ## dbg('finished IsInBounds', indent=0)
return min <= value <= max return min <= value <= max
@ -1383,21 +1386,21 @@ class MaskedNumCtrl(BaseMaskedTextCtrl, MaskedNumCtrlAccessorsMixin):
type and bounds checking and raises ValueError if argument is type and bounds checking and raises ValueError if argument is
not a valid value. not a valid value.
""" """
dbg('MaskedNumCtrl::_toGUI(%s)' % repr(value), indent=1) ## dbg('NumCtrl::_toGUI(%s)' % repr(value), indent=1)
if value is None and self.IsNoneAllowed(): if value is None and self.IsNoneAllowed():
dbg(indent=0) ## dbg(indent=0)
return self._template return self._template
elif type(value) in (types.StringType, types.UnicodeType): elif type(value) in (types.StringType, types.UnicodeType):
value = self._GetNumValue(value) value = self._GetNumValue(value)
dbg('cleansed num value: "%s"' % value) ## dbg('cleansed num value: "%s"' % value)
if value == "": if value == "":
if self.IsNoneAllowed(): if self.IsNoneAllowed():
dbg(indent=0) ## dbg(indent=0)
return self._template return self._template
else: else:
dbg('exception raised:', e, indent=0) ## dbg('exception raised:', e, indent=0)
raise ValueError ('wxMaskedNumCtrl requires numeric value, passed %s'% repr(value) ) raise ValueError ('NumCtrl requires numeric value, passed %s'% repr(value) )
# else... # else...
try: try:
if self._fractionWidth or value.find('.') != -1: if self._fractionWidth or value.find('.') != -1:
@ -1405,13 +1408,13 @@ class MaskedNumCtrl(BaseMaskedTextCtrl, MaskedNumCtrlAccessorsMixin):
else: else:
value = long(value) value = long(value)
except Exception, e: except Exception, e:
dbg('exception raised:', e, indent=0) ## dbg('exception raised:', e, indent=0)
raise ValueError ('MaskedNumCtrl requires numeric value, passed %s'% repr(value) ) raise ValueError ('NumCtrl requires numeric value, passed %s'% repr(value) )
elif type(value) not in (types.IntType, types.LongType, types.FloatType): elif type(value) not in (types.IntType, types.LongType, types.FloatType):
dbg(indent=0) ## dbg(indent=0)
raise ValueError ( raise ValueError (
'MaskedNumCtrl requires numeric value, passed %s'% repr(value) ) 'NumCtrl requires numeric value, passed %s'% repr(value) )
if not self._allowNegative and value < 0: if not self._allowNegative and value < 0:
raise ValueError ( raise ValueError (
@ -1421,29 +1424,29 @@ class MaskedNumCtrl(BaseMaskedTextCtrl, MaskedNumCtrlAccessorsMixin):
min = self.GetMin() min = self.GetMin()
max = self.GetMax() max = self.GetMax()
if not min is None and value < min: if not min is None and value < min:
dbg(indent=0) ## dbg(indent=0)
raise ValueError ( raise ValueError (
'value %d is below minimum value of control'% value ) 'value %d is below minimum value of control'% value )
if not max is None and value > max: if not max is None and value > max:
dbg(indent=0) ## dbg(indent=0)
raise ValueError ( raise ValueError (
'value %d exceeds value of control'% value ) 'value %d exceeds value of control'% value )
adjustwidth = len(self._mask) - (1 * self._useParens * self._signOk) adjustwidth = len(self._mask) - (1 * self._useParens * self._signOk)
dbg('len(%s):' % self._mask, len(self._mask)) ## dbg('len(%s):' % self._mask, len(self._mask))
dbg('adjustwidth - groupSpace:', adjustwidth - self._groupSpace) ## dbg('adjustwidth - groupSpace:', adjustwidth - self._groupSpace)
dbg('adjustwidth:', adjustwidth) ## dbg('adjustwidth:', adjustwidth)
if self._fractionWidth == 0: if self._fractionWidth == 0:
s = str(long(value)).rjust(self._integerWidth) s = str(long(value)).rjust(self._integerWidth)
else: else:
format = '%' + '%d.%df' % (self._integerWidth+self._fractionWidth+1, self._fractionWidth) format = '%' + '%d.%df' % (self._integerWidth+self._fractionWidth+1, self._fractionWidth)
s = format % float(value) s = format % float(value)
dbg('s:"%s"' % s, 'len(s):', len(s)) ## dbg('s:"%s"' % s, 'len(s):', len(s))
if len(s) > (adjustwidth - self._groupSpace): if len(s) > (adjustwidth - self._groupSpace):
dbg(indent=0) ## dbg(indent=0)
raise ValueError ('value %s exceeds the integer width of the control (%d)' % (s, self._integerWidth)) raise ValueError ('value %s exceeds the integer width of the control (%d)' % (s, self._integerWidth))
elif s[0] not in ('-', ' ') and self._allowNegative and len(s) == (adjustwidth - self._groupSpace): elif s[0] not in ('-', ' ') and self._allowNegative and len(s) == (adjustwidth - self._groupSpace):
dbg(indent=0) ## dbg(indent=0)
raise ValueError ('value %s exceeds the integer width of the control (%d)' % (s, self._integerWidth)) raise ValueError ('value %s exceeds the integer width of the control (%d)' % (s, self._integerWidth))
s = s.rjust(adjustwidth).replace('.', self._decimalChar) s = s.rjust(adjustwidth).replace('.', self._decimalChar)
@ -1452,7 +1455,7 @@ class MaskedNumCtrl(BaseMaskedTextCtrl, MaskedNumCtrlAccessorsMixin):
s = s.replace('-', '(') + ')' s = s.replace('-', '(') + ')'
else: else:
s += ' ' s += ' '
dbg('returned: "%s"' % s, indent=0) ## dbg('returned: "%s"' % s, indent=0)
return s return s
@ -1460,8 +1463,8 @@ class MaskedNumCtrl(BaseMaskedTextCtrl, MaskedNumCtrlAccessorsMixin):
""" """
Conversion function used in getting the value of the control. Conversion function used in getting the value of the control.
""" """
dbg(suspend=0) ## dbg(suspend=0)
dbg('MaskedNumCtrl::_fromGUI(%s)' % value, indent=1) ## dbg('NumCtrl::_fromGUI(%s)' % value, indent=1)
# One or more of the underlying text control implementations # One or more of the underlying text control implementations
# issue an intermediate EVT_TEXT when replacing the control's # issue an intermediate EVT_TEXT when replacing the control's
# value, where the intermediate value is an empty string. # value, where the intermediate value is an empty string.
@ -1470,42 +1473,42 @@ class MaskedNumCtrl(BaseMaskedTextCtrl, MaskedNumCtrlAccessorsMixin):
# #
if value.strip() == '': if value.strip() == '':
if not self.IsNoneAllowed(): if not self.IsNoneAllowed():
dbg('empty value; not allowed,returning 0', indent = 0) ## dbg('empty value; not allowed,returning 0', indent = 0)
if self._fractionWidth: if self._fractionWidth:
return 0.0 return 0.0
else: else:
return 0 return 0
else: else:
dbg('empty value; returning None', indent = 0) ## dbg('empty value; returning None', indent = 0)
return None return None
else: else:
value = self._GetNumValue(value) value = self._GetNumValue(value)
dbg('Num value: "%s"' % value) ## dbg('Num value: "%s"' % value)
if self._fractionWidth: if self._fractionWidth:
try: try:
dbg(indent=0) ## dbg(indent=0)
return float( value ) return float( value )
except ValueError: except ValueError:
dbg("couldn't convert to float; returning None") ## dbg("couldn't convert to float; returning None")
return None return None
else: else:
raise raise
else: else:
try: try:
dbg(indent=0) ## dbg(indent=0)
return int( value ) return int( value )
except ValueError: except ValueError:
try: try:
dbg(indent=0) ## dbg(indent=0)
return long( value ) return long( value )
except ValueError: except ValueError:
dbg("couldn't convert to long; returning None") ## dbg("couldn't convert to long; returning None")
return None return None
else: else:
raise raise
else: else:
dbg('exception occurred; returning None') ## dbg('exception occurred; returning None')
return None return None
@ -1514,7 +1517,7 @@ class MaskedNumCtrl(BaseMaskedTextCtrl, MaskedNumCtrlAccessorsMixin):
Preprocessor for base control paste; if value needs to be right-justified Preprocessor for base control paste; if value needs to be right-justified
to fit in control, do so prior to paste: to fit in control, do so prior to paste:
""" """
dbg('MaskedNumCtrl::_Paste (value = "%s")' % value) ## dbg('NumCtrl::_Paste (value = "%s")' % value)
if value is None: if value is None:
paste_text = self._getClipboardContents() paste_text = self._getClipboardContents()
else: else:
@ -1545,7 +1548,7 @@ if __name__ == '__main__':
style = wx.DEFAULT_DIALOG_STYLE ): style = wx.DEFAULT_DIALOG_STYLE ):
wx.Dialog.__init__(self, parent, id, title, pos, size, style) wx.Dialog.__init__(self, parent, id, title, pos, size, style)
self.int_ctrl = MaskedNumCtrl(self, wx.NewId(), size=(55,20)) self.int_ctrl = NumCtrl(self, wx.NewId(), size=(55,20))
self.OK = wx.Button( self, wx.ID_OK, "OK") self.OK = wx.Button( self, wx.ID_OK, "OK")
self.Cancel = wx.Button( self, wx.ID_CANCEL, "Cancel") self.Cancel = wx.Button( self, wx.ID_CANCEL, "Cancel")
@ -1560,7 +1563,7 @@ if __name__ == '__main__':
self.SetSizer( vs ) self.SetSizer( vs )
vs.Fit( self ) vs.Fit( self )
vs.SetSizeHints( self ) vs.SetSizeHints( self )
self.Bind(EVT_MASKEDNUM, self.OnChange, self.int_ctrl) self.Bind(EVT_NUM, self.OnChange, self.int_ctrl)
def OnChange(self, event): def OnChange(self, event):
print 'value now', event.GetValue() print 'value now', event.GetValue()
@ -1578,7 +1581,7 @@ if __name__ == '__main__':
return True return True
def OnClick(self, event): def OnClick(self, event):
dlg = myDialog(self.panel, -1, "test MaskedNumCtrl") dlg = myDialog(self.panel, -1, "test NumCtrl")
dlg.int_ctrl.SetValue(501) dlg.int_ctrl.SetValue(501)
dlg.int_ctrl.SetInsertionPoint(1) dlg.int_ctrl.SetInsertionPoint(1)
dlg.int_ctrl.SetSelection(1,2) dlg.int_ctrl.SetSelection(1,2)

View File

@ -0,0 +1,325 @@
#----------------------------------------------------------------------------
# Name: masked.textctrl.py
# Authors: Jeff Childers, Will Sadkin
# Email: jchilders_98@yahoo.com, wsadkin@nameconnector.com
# Created: 02/11/2003
# Copyright: (c) 2003 by Jeff Childers, Will Sadkin, 2003
# Portions: (c) 2002 by Will Sadkin, 2002-2003
# RCS-ID: $Id$
# License: wxWidgets license
#----------------------------------------------------------------------------
#
# This file contains the most typically used generic masked control,
# masked.TextCtrl. It also defines the BaseMaskedTextCtrl, which can
# be used to derive other "semantics-specific" classes, like masked.NumCtrl,
# masked.TimeCtrl, and masked.IpAddrCtrl.
#
#----------------------------------------------------------------------------
import wx
from wx.lib.masked import *
# jmg 12/9/03 - when we cut ties with Py 2.2 and earlier, this would
# be a good place to implement the 2.3 logger class
from wx.tools.dbg import Logger
dbg = Logger()
##dbg(enable=0)
# ## TRICKY BIT: to avoid a ton of boiler-plate, and to
# ## automate the getter/setter generation for each valid
# ## control parameter so we never forget to add the
# ## functions when adding parameters, this loop
# ## programmatically adds them to the class:
# ## (This makes it easier for Designers like Boa to
# ## deal with masked controls.)
#
# ## To further complicate matters, this is done with an
# ## extra level of inheritance, so that "general" classes like
# ## MaskedTextCtrl can have all possible attributes,
# ## while derived classes, like TimeCtrl and MaskedNumCtrl
# ## can prevent exposure of those optional attributes of their base
# ## class that do not make sense for their derivation. Therefore,
# ## we define
# ## BaseMaskedTextCtrl(TextCtrl, MaskedEditMixin)
# ## and
# ## MaskedTextCtrl(BaseMaskedTextCtrl, MaskedEditAccessorsMixin).
# ##
# ## This allows us to then derive:
# ## MaskedNumCtrl( BaseMaskedTextCtrl )
# ##
# ## and not have to expose all the same accessor functions for the
# ## derived control when they don't all make sense for it.
# ##
class BaseMaskedTextCtrl( wx.TextCtrl, MaskedEditMixin ):
"""
This is the primary derivation from MaskedEditMixin. It provides
a general masked text control that can be configured with different
masks. It's actually a "base masked textCtrl", so that the
MaskedTextCtrl class can be derived from it, and add those
accessor functions to it that are appropriate to the general class,
whilst other classes can derive from BaseMaskedTextCtrl, and
only define those accessor functions that are appropriate for
those derivations.
"""
def __init__( self, parent, id=-1, value = '',
pos = wx.DefaultPosition,
size = wx.DefaultSize,
style = wx.TE_PROCESS_TAB,
validator=wx.DefaultValidator, ## placeholder provided for data-transfer logic
name = 'maskedTextCtrl',
setupEventHandling = True, ## setup event handling by default
**kwargs):
wx.TextCtrl.__init__(self, parent, id, value='',
pos=pos, size = size,
style=style, validator=validator,
name=name)
self.controlInitialized = True
MaskedEditMixin.__init__( self, name, **kwargs )
self._SetInitialValue(value)
if setupEventHandling:
## Setup event handlers
self.Bind(wx.EVT_SET_FOCUS, self._OnFocus ) ## defeat automatic full selection
self.Bind(wx.EVT_KILL_FOCUS, self._OnKillFocus ) ## run internal validator
self.Bind(wx.EVT_LEFT_DCLICK, self._OnDoubleClick) ## select field under cursor on dclick
self.Bind(wx.EVT_RIGHT_UP, self._OnContextMenu ) ## bring up an appropriate context menu
self.Bind(wx.EVT_KEY_DOWN, self._OnKeyDown ) ## capture control events not normally seen, eg ctrl-tab.
self.Bind(wx.EVT_CHAR, self._OnChar ) ## handle each keypress
self.Bind(wx.EVT_TEXT, self._OnTextChange ) ## color control appropriately & keep
## track of previous value for undo
def __repr__(self):
return "<BaseMaskedTextCtrl: %s>" % self.GetValue()
def _GetSelection(self):
"""
Allow mixin to get the text selection of this control.
REQUIRED by any class derived from MaskedEditMixin.
"""
return self.GetSelection()
def _SetSelection(self, sel_start, sel_to):
"""
Allow mixin to set the text selection of this control.
REQUIRED by any class derived from MaskedEditMixin.
"""
#### dbg("MaskedTextCtrl::_SetSelection(%(sel_start)d, %(sel_to)d)" % locals())
return self.SetSelection( sel_start, sel_to )
def SetSelection(self, sel_start, sel_to):
"""
This is just for debugging...
"""
## dbg("MaskedTextCtrl::SetSelection(%(sel_start)d, %(sel_to)d)" % locals())
wx.TextCtrl.SetSelection(self, sel_start, sel_to)
def _GetInsertionPoint(self):
return self.GetInsertionPoint()
def _SetInsertionPoint(self, pos):
#### dbg("MaskedTextCtrl::_SetInsertionPoint(%(pos)d)" % locals())
self.SetInsertionPoint(pos)
def SetInsertionPoint(self, pos):
"""
This is just for debugging...
"""
## dbg("MaskedTextCtrl::SetInsertionPoint(%(pos)d)" % locals())
wx.TextCtrl.SetInsertionPoint(self, pos)
def _GetValue(self):
"""
Allow mixin to get the raw value of the control with this function.
REQUIRED by any class derived from MaskedEditMixin.
"""
return self.GetValue()
def _SetValue(self, value):
"""
Allow mixin to set the raw value of the control with this function.
REQUIRED by any class derived from MaskedEditMixin.
"""
## dbg('MaskedTextCtrl::_SetValue("%(value)s")' % locals(), indent=1)
# Record current selection and insertion point, for undo
self._prevSelection = self._GetSelection()
self._prevInsertionPoint = self._GetInsertionPoint()
wx.TextCtrl.SetValue(self, value)
## dbg(indent=0)
def SetValue(self, value):
"""
This function redefines the externally accessible .SetValue to be
a smart "paste" of the text in question, so as not to corrupt the
masked control. NOTE: this must be done in the class derived
from the base wx control.
"""
## dbg('MaskedTextCtrl::SetValue = "%s"' % value, indent=1)
if not self._mask:
wx.TextCtrl.SetValue(self, value) # revert to base control behavior
return
# empty previous contents, replacing entire value:
self._SetInsertionPoint(0)
self._SetSelection(0, self._masklength)
if self._signOk and self._useParens:
signpos = value.find('-')
if signpos != -1:
value = value[:signpos] + '(' + value[signpos+1:].strip() + ')'
elif value.find(')') == -1 and len(value) < self._masklength:
value += ' ' # add place holder for reserved space for right paren
if( len(value) < self._masklength # value shorter than control
and (self._isFloat or self._isInt) # and it's a numeric control
and self._ctrl_constraints._alignRight ): # and it's a right-aligned control
## dbg('len(value)', len(value), ' < self._masklength', self._masklength)
# try to intelligently "pad out" the value to the right size:
value = self._template[0:self._masklength - len(value)] + value
if self._isFloat and value.find('.') == -1:
value = value[1:]
## dbg('padded value = "%s"' % value)
# make SetValue behave the same as if you had typed the value in:
try:
value = self._Paste(value, raise_on_invalid=True, just_return_value=True)
if self._isFloat:
self._isNeg = False # (clear current assumptions)
value = self._adjustFloat(value)
elif self._isInt:
self._isNeg = False # (clear current assumptions)
value = self._adjustInt(value)
elif self._isDate and not self.IsValid(value) and self._4digityear:
value = self._adjustDate(value, fixcentury=True)
except ValueError:
# If date, year might be 2 digits vs. 4; try adjusting it:
if self._isDate and self._4digityear:
dateparts = value.split(' ')
dateparts[0] = self._adjustDate(dateparts[0], fixcentury=True)
value = string.join(dateparts, ' ')
## dbg('adjusted value: "%s"' % value)
value = self._Paste(value, raise_on_invalid=True, just_return_value=True)
else:
## dbg('exception thrown', indent=0)
raise
self._SetValue(value) # note: to preserve similar capability, .SetValue()
# does not change IsModified()
#### dbg('queuing insertion after .SetValue', self._masklength)
wx.CallAfter(self._SetInsertionPoint, self._masklength)
wx.CallAfter(self._SetSelection, self._masklength, self._masklength)
## dbg(indent=0)
def Clear(self):
""" Blanks the current control value by replacing it with the default value."""
## dbg("MaskedTextCtrl::Clear - value reset to default value (template)")
if self._mask:
self.ClearValue()
else:
wx.TextCtrl.Clear(self) # else revert to base control behavior
def _Refresh(self):
"""
Allow mixin to refresh the base control with this function.
REQUIRED by any class derived from MaskedEditMixin.
"""
## dbg('MaskedTextCtrl::_Refresh', indent=1)
wx.TextCtrl.Refresh(self)
## dbg(indent=0)
def Refresh(self):
"""
This function redefines the externally accessible .Refresh() to
validate the contents of the masked control as it refreshes.
NOTE: this must be done in the class derived from the base wx control.
"""
## dbg('MaskedTextCtrl::Refresh', indent=1)
self._CheckValid()
self._Refresh()
## dbg(indent=0)
def _IsEditable(self):
"""
Allow mixin to determine if the base control is editable with this function.
REQUIRED by any class derived from MaskedEditMixin.
"""
return wx.TextCtrl.IsEditable(self)
def Cut(self):
"""
This function redefines the externally accessible .Cut to be
a smart "erase" of the text in question, so as not to corrupt the
masked control. NOTE: this must be done in the class derived
from the base wx control.
"""
if self._mask:
self._Cut() # call the mixin's Cut method
else:
wx.TextCtrl.Cut(self) # else revert to base control behavior
def Paste(self):
"""
This function redefines the externally accessible .Paste to be
a smart "paste" of the text in question, so as not to corrupt the
masked control. NOTE: this must be done in the class derived
from the base wx control.
"""
if self._mask:
self._Paste() # call the mixin's Paste method
else:
wx.TextCtrl.Paste(self, value) # else revert to base control behavior
def Undo(self):
"""
This function defines the undo operation for the control. (The default
undo is 1-deep.)
"""
if self._mask:
self._Undo()
else:
wx.TextCtrl.Undo(self) # else revert to base control behavior
def IsModified(self):
"""
This function overrides the raw wxTextCtrl method, because the
masked edit mixin uses SetValue to change the value, which doesn't
modify the state of this attribute. So, we keep track on each
keystroke to see if the value changes, and if so, it's been
modified.
"""
return wx.TextCtrl.IsModified(self) or self.modified
def _CalcSize(self, size=None):
"""
Calculate automatic size if allowed; use base mixin function.
"""
return self._calcSize(size)
class TextCtrl( BaseMaskedTextCtrl, MaskedEditAccessorsMixin ):
"""
This extra level of inheritance allows us to add the generic set of
masked edit parameters only to this class while allowing other
classes to derive from the "base" masked text control, and provide
a smaller set of valid accessor functions.
"""
pass

View File

@ -36,11 +36,11 @@
# o wx.SpinCtl has some issues that cause the control to # o wx.SpinCtl has some issues that cause the control to
# lock up. Noted in other places using it too, it's not this module # lock up. Noted in other places using it too, it's not this module
# that's at fault. # that's at fault.
# #
# 12/20/2003 - Jeff Grimmett (grimmtooth@softhome.net) # 12/20/2003 - Jeff Grimmett (grimmtooth@softhome.net)
# #
# o wxMaskedTextCtrl -> MaskedTextCtrl # o wxMaskedTextCtrl -> masked.TextCtrl
# o wxTimeCtrl -> TimeCtrl # o wxTimeCtrl -> masked.TimeCtrl
# #
"""<html><body> """<html><body>
@ -65,7 +65,7 @@ Here's the API for TimeCtrl:
<B>style</B> = wxTE_PROCESS_TAB, <B>style</B> = wxTE_PROCESS_TAB,
<B>validator</B> = wx.DefaultValidator, <B>validator</B> = wx.DefaultValidator,
name = "time", name = "time",
<B>format</B> = 'HHMMSS', <B>format</B> = 'HHMMSS',
<B>fmt24hr</B> = False, <B>fmt24hr</B> = False,
<B>displaySeconds</B> = True, <B>displaySeconds</B> = True,
<B>spinButton</B> = None, <B>spinButton</B> = None,
@ -95,7 +95,7 @@ Here's the API for TimeCtrl:
<DD>This parameter can be used instead of the fmt24hr and displaySeconds <DD>This parameter can be used instead of the fmt24hr and displaySeconds
parameters, respectively; it provides a shorthand way to specify the time parameters, respectively; it provides a shorthand way to specify the time
format you want. Accepted values are 'HHMMSS', 'HHMM', '24HHMMSS', and format you want. Accepted values are 'HHMMSS', 'HHMM', '24HHMMSS', and
'24HHMM'. If the format is specified, the other two arguments will be ignored. '24HHMM'. If the format is specified, the other two arguments will be ignored.
<BR> <BR>
<DT><B>fmt24hr</B> <DT><B>fmt24hr</B>
<DD>If True, control will display time in 24 hour time format; if False, it will <DD>If True, control will display time in 24 hour time format; if False, it will
@ -107,7 +107,7 @@ Here's the API for TimeCtrl:
<DD>If True, control will include a seconds field; if False, it will <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> just show hours and minutes. (This value is ignored if the <i>format</i>
parameter is specified.) parameter is specified.)
<BR> <BR>
<DT><B>spinButton</B> <DT><B>spinButton</B>
<DD>If specified, this button's events will be bound to the behavior of the <DD>If specified, this button's events will be bound to the behavior of the
TimeCtrl, working like up/down cursor key events. (See BindSpinButton.) TimeCtrl, working like up/down cursor key events. (See BindSpinButton.)
@ -272,10 +272,10 @@ import types
import wx import wx
from wx.tools.dbg import Logger from wx.tools.dbg import Logger
from wx.lib.maskededit import BaseMaskedTextCtrl, Field from wx.lib.masked import Field, BaseMaskedTextCtrl
dbg = Logger() dbg = Logger()
dbg(enable=0) ##dbg(enable=0)
try: try:
from mx import DateTime from mx import DateTime
@ -322,8 +322,8 @@ class TimeCtrlAccessorsMixin:
exec('def Set%s(self, value): self.SetCtrlParameters(%s=value)' % (propname, param)) exec('def Set%s(self, value): self.SetCtrlParameters(%s=value)' % (propname, param))
exec('def Get%s(self): return self.GetCtrlParameter("%s")''' % (propname, param)) exec('def Get%s(self): return self.GetCtrlParameter("%s")''' % (propname, param))
class TimeCtrl(BaseMaskedTextCtrl): class TimeCtrl(BaseMaskedTextCtrl):
valid_ctrl_params = { valid_ctrl_params = {
@ -333,7 +333,7 @@ class TimeCtrl(BaseMaskedTextCtrl):
'max': None, 'max': None,
'limited': False, # by default, no limiting even if bounds set 'limited': False, # by default, no limiting even if bounds set
'useFixedWidthFont': True, # by default, use a fixed-width font 'useFixedWidthFont': True, # by default, use a fixed-width font
'oob_color': "Yellow" # by default, the default MaskedTextCtrl "invalid" color 'oob_color': "Yellow" # by default, the default masked.TextCtrl "invalid" color
} }
def __init__ ( def __init__ (
@ -347,7 +347,7 @@ class TimeCtrl(BaseMaskedTextCtrl):
**kwargs ): **kwargs ):
# set defaults for control: # set defaults for control:
dbg('setting defaults:') ## dbg('setting defaults:')
for key, param_value in TimeCtrl.valid_ctrl_params.items(): for key, param_value in TimeCtrl.valid_ctrl_params.items():
# This is done this way to make setattr behave consistently with # This is done this way to make setattr behave consistently with
# "private attribute" name mangling # "private attribute" name mangling
@ -456,7 +456,7 @@ class TimeCtrl(BaseMaskedTextCtrl):
def SetParameters(self, **kwargs): def SetParameters(self, **kwargs):
dbg('TimeCtrl::SetParameters(%s)' % repr(kwargs), indent=1) ## dbg('TimeCtrl::SetParameters(%s)' % repr(kwargs), indent=1)
maskededit_kwargs = {} maskededit_kwargs = {}
reset_format = False reset_format = False
@ -553,10 +553,10 @@ class TimeCtrl(BaseMaskedTextCtrl):
self.SetValue(value) self.SetValue(value)
except: except:
self.SetValue('12:00:00 AM') self.SetValue('12:00:00 AM')
dbg(indent=0) ## dbg(indent=0)
return {} # no arguments to return return {} # no arguments to return
else: else:
dbg(indent=0) ## dbg(indent=0)
return maskededit_kwargs return maskededit_kwargs
@ -565,7 +565,7 @@ class TimeCtrl(BaseMaskedTextCtrl):
This function binds an externally created spin button to the control, so that This function binds an externally created spin button to the control, so that
up/down events from the button automatically change the control. up/down events from the button automatically change the control.
""" """
dbg('TimeCtrl::BindSpinButton') ## dbg('TimeCtrl::BindSpinButton')
self.__spinButton = sb self.__spinButton = sb
if self.__spinButton: if self.__spinButton:
# bind event handlers to spin ctrl # bind event handlers to spin ctrl
@ -584,16 +584,16 @@ class TimeCtrl(BaseMaskedTextCtrl):
and convert wxDateTime, mxDateTime, or 12/24 format time string and convert wxDateTime, mxDateTime, or 12/24 format time string
into the appropriate format string for the control. into the appropriate format string for the control.
""" """
dbg('TimeCtrl::SetValue(%s)' % repr(value), indent=1) ## dbg('TimeCtrl::SetValue(%s)' % repr(value), indent=1)
try: try:
strtime = self._toGUI(self.__validateValue(value)) strtime = self._toGUI(self.__validateValue(value))
except: except:
dbg('validation failed', indent=0) ## dbg('validation failed', indent=0)
raise raise
dbg('strtime:', strtime) ## dbg('strtime:', strtime)
self._SetValue(strtime) self._SetValue(strtime)
dbg(indent=0) ## dbg(indent=0)
def GetValue(self, def GetValue(self,
as_wxDateTime = False, as_wxDateTime = False,
@ -641,12 +641,12 @@ class TimeCtrl(BaseMaskedTextCtrl):
raised. raised.
""" """
global accept_mx global accept_mx
dbg(suspend=1) ## dbg(suspend=1)
dbg('TimeCtrl::GetWxDateTime(%s)' % repr(value), indent=1) ## dbg('TimeCtrl::GetWxDateTime(%s)' % repr(value), indent=1)
if value is None: if value is None:
dbg('getting control value') ## dbg('getting control value')
value = self.GetValue() value = self.GetValue()
dbg('value = "%s"' % value) ## dbg('value = "%s"' % value)
if type(value) == types.UnicodeType: if type(value) == types.UnicodeType:
value = str(value) # convert to regular string value = str(value) # convert to regular string
@ -656,14 +656,14 @@ class TimeCtrl(BaseMaskedTextCtrl):
# Construct constant wxDateTime, then try to parse the string: # Construct constant wxDateTime, then try to parse the string:
wxdt = wx.DateTimeFromDMY(1, 0, 1970) wxdt = wx.DateTimeFromDMY(1, 0, 1970)
dbg('attempting conversion') ## dbg('attempting conversion')
value = value.strip() # (parser doesn't like leading spaces) value = value.strip() # (parser doesn't like leading spaces)
checkTime = wxdt.ParseTime(value) checkTime = wxdt.ParseTime(value)
valid = checkTime == len(value) # entire string parsed? valid = checkTime == len(value) # entire string parsed?
dbg('checkTime == len(value)?', valid) ## dbg('checkTime == len(value)?', valid)
if not valid: if not valid:
dbg(indent=0, suspend=0) ## dbg(indent=0, suspend=0)
raise ValueError('cannot convert string "%s" to valid time' % value) raise ValueError('cannot convert string "%s" to valid time' % value)
else: else:
@ -685,7 +685,7 @@ class TimeCtrl(BaseMaskedTextCtrl):
error = 'GetWxDateTime requires wxDateTime, mxDateTime or parsable time string, passed %s'% repr(value) error = 'GetWxDateTime requires wxDateTime, mxDateTime or parsable time string, passed %s'% repr(value)
else: else:
error = 'GetWxDateTime requires wxDateTime or parsable time string, passed %s'% repr(value) error = 'GetWxDateTime requires wxDateTime or parsable time string, passed %s'% repr(value)
dbg(indent=0, suspend=0) ## dbg(indent=0, suspend=0)
raise ValueError(error) raise ValueError(error)
wxdt = wx.DateTimeFromDMY(1, 0, 1970) wxdt = wx.DateTimeFromDMY(1, 0, 1970)
@ -693,7 +693,7 @@ class TimeCtrl(BaseMaskedTextCtrl):
wxdt.SetMinute(minute) wxdt.SetMinute(minute)
wxdt.SetSecond(second) wxdt.SetSecond(second)
dbg('wxdt:', wxdt, indent=0, suspend=0) ## dbg('wxdt:', wxdt, indent=0, suspend=0)
return wxdt return wxdt
@ -730,13 +730,13 @@ class TimeCtrl(BaseMaskedTextCtrl):
adjusted to the new minimum value; if not limited, the value in the adjusted to the new minimum value; if not limited, the value in the
control will be colored as invalid. control will be colored as invalid.
""" """
dbg('TimeCtrl::SetMin(%s)'% repr(min), indent=1) ## dbg('TimeCtrl::SetMin(%s)'% repr(min), indent=1)
if min is not None: if min is not None:
try: try:
min = self.GetWxDateTime(min) min = self.GetWxDateTime(min)
self.__min = self._toGUI(min) self.__min = self._toGUI(min)
except: except:
dbg('exception occurred', indent=0) ## dbg('exception occurred', indent=0)
return False return False
else: else:
self.__min = min self.__min = min
@ -746,7 +746,7 @@ class TimeCtrl(BaseMaskedTextCtrl):
else: else:
self._CheckValid() self._CheckValid()
ret = True ret = True
dbg('ret:', ret, indent=0) ## dbg('ret:', ret, indent=0)
return ret return ret
@ -757,22 +757,23 @@ class TimeCtrl(BaseMaskedTextCtrl):
the current minimum bound on the control, as a wxDateTime the current minimum bound on the control, as a wxDateTime
by default, or as a string if as_string argument is True. by default, or as a string if as_string argument is True.
""" """
dbg(suspend=1) ## dbg(suspend=1)
dbg('TimeCtrl::GetMin, as_string?', as_string, indent=1) ## dbg('TimeCtrl::GetMin, as_string?', as_string, indent=1)
if self.__min is None: if self.__min is None:
dbg('(min == None)') ## dbg('(min == None)')
ret = self.__min ret = self.__min
elif as_string: elif as_string:
ret = self.__min ret = self.__min
dbg('ret:', ret) ## dbg('ret:', ret)
else: else:
try: try:
ret = self.GetWxDateTime(self.__min) ret = self.GetWxDateTime(self.__min)
except: except:
dbg(suspend=0) ## dbg(suspend=0)
dbg('exception occurred', indent=0) ## dbg('exception occurred', indent=0)
dbg('ret:', repr(ret)) raise
dbg(indent=0, suspend=0) ## dbg('ret:', repr(ret))
## dbg(indent=0, suspend=0)
return ret return ret
@ -789,23 +790,23 @@ class TimeCtrl(BaseMaskedTextCtrl):
adjusted to this maximum value; if not limited, the value in the adjusted to this maximum value; if not limited, the value in the
control will be colored as invalid. control will be colored as invalid.
""" """
dbg('TimeCtrl::SetMax(%s)' % repr(max), indent=1) ## dbg('TimeCtrl::SetMax(%s)' % repr(max), indent=1)
if max is not None: if max is not None:
try: try:
max = self.GetWxDateTime(max) max = self.GetWxDateTime(max)
self.__max = self._toGUI(max) self.__max = self._toGUI(max)
except: except:
dbg('exception occurred', indent=0) ## dbg('exception occurred', indent=0)
return False return False
else: else:
self.__max = max self.__max = max
dbg('max:', repr(self.__max)) ## dbg('max:', repr(self.__max))
if self.IsLimited() and not self.IsInBounds(): if self.IsLimited() and not self.IsInBounds():
self.SetLimited(self.__limited) # force limited value: self.SetLimited(self.__limited) # force limited value:
else: else:
self._CheckValid() self._CheckValid()
ret = True ret = True
dbg('ret:', ret, indent=0) ## dbg('ret:', ret, indent=0)
return ret return ret
@ -816,23 +817,23 @@ class TimeCtrl(BaseMaskedTextCtrl):
the current minimum bound on the control, as a wxDateTime the current minimum bound on the control, as a wxDateTime
by default, or as a string if as_string argument is True. by default, or as a string if as_string argument is True.
""" """
dbg(suspend=1) ## dbg(suspend=1)
dbg('TimeCtrl::GetMin, as_string?', as_string, indent=1) ## dbg('TimeCtrl::GetMin, as_string?', as_string, indent=1)
if self.__max is None: if self.__max is None:
dbg('(max == None)') ## dbg('(max == None)')
ret = self.__max ret = self.__max
elif as_string: elif as_string:
ret = self.__max ret = self.__max
dbg('ret:', ret) ## dbg('ret:', ret)
else: else:
try: try:
ret = self.GetWxDateTime(self.__max) ret = self.GetWxDateTime(self.__max)
except: except:
dbg(suspend=0) ## dbg(suspend=0)
dbg('exception occurred', indent=0) ## dbg('exception occurred', indent=0)
raise raise
dbg('ret:', repr(ret)) ## dbg('ret:', repr(ret))
dbg(indent=0, suspend=0) ## dbg(indent=0, suspend=0)
return ret return ret
@ -868,22 +869,22 @@ class TimeCtrl(BaseMaskedTextCtrl):
limiting, but coloring of out-of-bounds values will still take limiting, but coloring of out-of-bounds values will still take
place if bounds have been set for the control. place if bounds have been set for the control.
""" """
dbg('TimeCtrl::SetLimited(%d)' % limited, indent=1) ## dbg('TimeCtrl::SetLimited(%d)' % limited, indent=1)
self.__limited = limited self.__limited = limited
if not limited: if not limited:
self.SetMaskParameters(validRequired = False) self.SetMaskParameters(validRequired = False)
self._CheckValid() self._CheckValid()
dbg(indent=0) ## dbg(indent=0)
return return
dbg('requiring valid value') ## dbg('requiring valid value')
self.SetMaskParameters(validRequired = True) self.SetMaskParameters(validRequired = True)
min = self.GetMin() min = self.GetMin()
max = self.GetMax() max = self.GetMax()
if min is None or max is None: if min is None or max is None:
dbg('both bounds not set; no further action taken') ## dbg('both bounds not set; no further action taken')
return # can't limit without 2 bounds return # can't limit without 2 bounds
elif not self.IsInBounds(): elif not self.IsInBounds():
@ -891,11 +892,11 @@ class TimeCtrl(BaseMaskedTextCtrl):
try: try:
value = self.GetWxDateTime() value = self.GetWxDateTime()
except: except:
dbg('exception occurred', indent=0) ## dbg('exception occurred', indent=0)
raise raise
if min <= max: # valid range doesn't span midnight if min <= max: # valid range doesn't span midnight
dbg('min <= max') ## dbg('min <= max')
# which makes the "nearest bound" computation trickier... # which makes the "nearest bound" computation trickier...
# determine how long the "invalid" pie wedge is, and cut # determine how long the "invalid" pie wedge is, and cut
@ -918,24 +919,24 @@ class TimeCtrl(BaseMaskedTextCtrl):
cmp_value = value cmp_value = value
if (cmp_value - max) > half_interval: if (cmp_value - max) > half_interval:
dbg('forcing value to min (%s)' % min.FormatTime()) ## dbg('forcing value to min (%s)' % min.FormatTime())
self.SetValue(min) self.SetValue(min)
else: else:
dbg('forcing value to max (%s)' % max.FormatTime()) ## dbg('forcing value to max (%s)' % max.FormatTime())
self.SetValue(max) self.SetValue(max)
else: else:
dbg('max < min') ## dbg('max < min')
# therefore max < value < min guaranteed to be true, # therefore max < value < min guaranteed to be true,
# so "nearest bound" calculation is much easier: # so "nearest bound" calculation is much easier:
if (value - max) >= (min - value): if (value - max) >= (min - value):
# current value closer to min; pick that edge of pie wedge # current value closer to min; pick that edge of pie wedge
dbg('forcing value to min (%s)' % min.FormatTime()) ## dbg('forcing value to min (%s)' % min.FormatTime())
self.SetValue(min) self.SetValue(min)
else: else:
dbg('forcing value to max (%s)' % max.FormatTime()) ## dbg('forcing value to max (%s)' % max.FormatTime())
self.SetValue(max) self.SetValue(max)
dbg(indent=0) ## dbg(indent=0)
@ -961,21 +962,22 @@ class TimeCtrl(BaseMaskedTextCtrl):
try: try:
value = self.GetWxDateTime(value) # try to regularize passed value value = self.GetWxDateTime(value) # try to regularize passed value
except ValueError: except ValueError:
dbg('ValueError getting wxDateTime for %s' % repr(value), indent=0) ## dbg('ValueError getting wxDateTime for %s' % repr(value), indent=0)
raise raise
dbg('TimeCtrl::IsInBounds(%s)' % repr(value), indent=1) ## dbg('TimeCtrl::IsInBounds(%s)' % repr(value), indent=1)
if self.__min is None or self.__max is None: if self.__min is None or self.__max is None:
dbg(indent=0) ## dbg(indent=0)
return True return True
elif value is None: elif value is None:
try: try:
value = self.GetWxDateTime() value = self.GetWxDateTime()
except: except:
dbg('exception occurred', indent=0) ## dbg('exception occurred', indent=0)
raise
dbg('value:', value.FormatTime()) ## dbg('value:', value.FormatTime())
# Get wxDateTime representations of bounds: # Get wxDateTime representations of bounds:
min = self.GetMin() min = self.GetMin()
@ -990,7 +992,7 @@ class TimeCtrl(BaseMaskedTextCtrl):
# either "min" <= value (<= midnight of *next day*) # either "min" <= value (<= midnight of *next day*)
# or midnight <= value <= "max" # or midnight <= value <= "max"
ret = min <= value or (midnight <= value <= max) ret = min <= value or (midnight <= value <= max)
dbg('in bounds?', ret, indent=0) ## dbg('in bounds?', ret, indent=0)
return ret return ret
@ -1021,7 +1023,7 @@ class TimeCtrl(BaseMaskedTextCtrl):
def __OnTextChange(self, event=None): def __OnTextChange(self, event=None):
dbg('TimeCtrl::OnTextChange', indent=1) ## dbg('TimeCtrl::OnTextChange', indent=1)
# Allow Maskedtext base control to color as appropriate, # Allow Maskedtext base control to color as appropriate,
# and Skip the EVT_TEXT event (if appropriate.) # and Skip the EVT_TEXT event (if appropriate.)
@ -1035,11 +1037,11 @@ class TimeCtrl(BaseMaskedTextCtrl):
if not BaseMaskedTextCtrl._OnTextChange(self, event): if not BaseMaskedTextCtrl._OnTextChange(self, event):
return return
dbg('firing TimeUpdatedEvent...') ## dbg('firing TimeUpdatedEvent...')
evt = TimeUpdatedEvent(self.GetId(), self.GetValue()) evt = TimeUpdatedEvent(self.GetId(), self.GetValue())
evt.SetEventObject(self) evt.SetEventObject(self)
self.GetEventHandler().ProcessEvent(evt) self.GetEventHandler().ProcessEvent(evt)
dbg(indent=0) ## dbg(indent=0)
def SetInsertionPoint(self, pos): def SetInsertionPoint(self, pos):
@ -1048,14 +1050,14 @@ class TimeCtrl(BaseMaskedTextCtrl):
This is necessary to handle the optional spin button, because the insertion This is necessary to handle the optional spin button, because the insertion
point is lost when the focus shifts to the spin button. point is lost when the focus shifts to the spin button.
""" """
dbg('TimeCtrl::SetInsertionPoint', pos, indent=1) ## dbg('TimeCtrl::SetInsertionPoint', pos, indent=1)
BaseMaskedTextCtrl.SetInsertionPoint(self, pos) # (causes EVT_TEXT event to fire) BaseMaskedTextCtrl.SetInsertionPoint(self, pos) # (causes EVT_TEXT event to fire)
self.__posCurrent = self.GetInsertionPoint() self.__posCurrent = self.GetInsertionPoint()
dbg(indent=0) ## dbg(indent=0)
def SetSelection(self, sel_start, sel_to): def SetSelection(self, sel_start, sel_to):
dbg('TimeCtrl::SetSelection', sel_start, sel_to, indent=1) ## dbg('TimeCtrl::SetSelection', sel_start, sel_to, indent=1)
# Adjust selection range to legal extent if not already # Adjust selection range to legal extent if not already
if sel_start < 0: if sel_start < 0:
@ -1069,7 +1071,7 @@ class TimeCtrl(BaseMaskedTextCtrl):
self.__bSelection = sel_start != sel_to self.__bSelection = sel_start != sel_to
BaseMaskedTextCtrl.SetSelection(self, sel_start, sel_to) BaseMaskedTextCtrl.SetSelection(self, sel_start, sel_to)
dbg(indent=0) ## dbg(indent=0)
def __OnSpin(self, key): def __OnSpin(self, key):
@ -1085,7 +1087,7 @@ class TimeCtrl(BaseMaskedTextCtrl):
start, end = self._FindField(self.__posCurrent)._extent start, end = self._FindField(self.__posCurrent)._extent
self.SetInsertionPoint(start) self.SetInsertionPoint(start)
self.SetSelection(start, end) self.SetSelection(start, end)
dbg('current position:', self.__posCurrent) ## dbg('current position:', self.__posCurrent)
def __OnSpinUp(self, event): def __OnSpinUp(self, event):
@ -1093,10 +1095,10 @@ class TimeCtrl(BaseMaskedTextCtrl):
Event handler for any bound spin button on EVT_SPIN_UP; Event handler for any bound spin button on EVT_SPIN_UP;
causes control to behave as if up arrow was pressed. causes control to behave as if up arrow was pressed.
""" """
dbg('TimeCtrl::OnSpinUp', indent=1) ## dbg('TimeCtrl::OnSpinUp', indent=1)
self.__OnSpin(wx.WXK_UP) self.__OnSpin(wx.WXK_UP)
keep_processing = False keep_processing = False
dbg(indent=0) ## dbg(indent=0)
return keep_processing return keep_processing
@ -1105,10 +1107,10 @@ class TimeCtrl(BaseMaskedTextCtrl):
Event handler for any bound spin button on EVT_SPIN_DOWN; Event handler for any bound spin button on EVT_SPIN_DOWN;
causes control to behave as if down arrow was pressed. causes control to behave as if down arrow was pressed.
""" """
dbg('TimeCtrl::OnSpinDown', indent=1) ## dbg('TimeCtrl::OnSpinDown', indent=1)
self.__OnSpin(wx.WXK_DOWN) self.__OnSpin(wx.WXK_DOWN)
keep_processing = False keep_processing = False
dbg(indent=0) ## dbg(indent=0)
return keep_processing return keep_processing
@ -1119,14 +1121,14 @@ class TimeCtrl(BaseMaskedTextCtrl):
It then calls the base control's _OnChar routine with the modified It then calls the base control's _OnChar routine with the modified
event instance. event instance.
""" """
dbg('TimeCtrl::OnChar', indent=1) ## dbg('TimeCtrl::OnChar', indent=1)
keycode = event.GetKeyCode() keycode = event.GetKeyCode()
dbg('keycode:', keycode) ## dbg('keycode:', keycode)
if keycode == ord(':'): if keycode == ord(':'):
dbg('colon seen! removing shift attribute') ## dbg('colon seen! removing shift attribute')
event.m_shiftDown = False event.m_shiftDown = False
BaseMaskedTextCtrl._OnChar(self, event ) ## handle each keypress BaseMaskedTextCtrl._OnChar(self, event ) ## handle each keypress
dbg(indent=0) ## dbg(indent=0)
def __OnSetToNow(self, event): def __OnSetToNow(self, event):
@ -1144,7 +1146,7 @@ class TimeCtrl(BaseMaskedTextCtrl):
Event handler for motion events; this handler Event handler for motion events; this handler
changes limits the selection to the new cell boundaries. changes limits the selection to the new cell boundaries.
""" """
dbg('TimeCtrl::LimitSelection', indent=1) ## dbg('TimeCtrl::LimitSelection', indent=1)
pos = self.GetInsertionPoint() pos = self.GetInsertionPoint()
self.__posCurrent = pos self.__posCurrent = pos
sel_start, sel_to = self.GetSelection() sel_start, sel_to = self.GetSelection()
@ -1155,18 +1157,18 @@ class TimeCtrl(BaseMaskedTextCtrl):
if sel_to < pos: sel_to = start if sel_to < pos: sel_to = start
elif sel_to > pos: sel_to = end elif sel_to > pos: sel_to = end
dbg('new pos =', self.__posCurrent, 'select to ', sel_to) ## dbg('new pos =', self.__posCurrent, 'select to ', sel_to)
self.SetInsertionPoint(self.__posCurrent) self.SetInsertionPoint(self.__posCurrent)
self.SetSelection(self.__posCurrent, sel_to) self.SetSelection(self.__posCurrent, sel_to)
if event: event.Skip() if event: event.Skip()
dbg(indent=0) ## dbg(indent=0)
def __IncrementValue(self, key, pos): def __IncrementValue(self, key, pos):
dbg('TimeCtrl::IncrementValue', key, pos, indent=1) ## dbg('TimeCtrl::IncrementValue', key, pos, indent=1)
text = self.GetValue() text = self.GetValue()
field = self._FindField(pos) field = self._FindField(pos)
dbg('field: ', field._index) ## dbg('field: ', field._index)
start, end = field._extent start, end = field._extent
slice = text[start:end] slice = text[start:end]
if key == wx.WXK_UP: increment = 1 if key == wx.WXK_UP: increment = 1
@ -1182,14 +1184,14 @@ class TimeCtrl(BaseMaskedTextCtrl):
# am/pm setting. So, we use wxDateTime to generate a new value for us: # am/pm setting. So, we use wxDateTime to generate a new value for us:
# (Use a fixed date not subject to DST variations:) # (Use a fixed date not subject to DST variations:)
converter = wx.DateTimeFromDMY(1, 0, 1970) converter = wx.DateTimeFromDMY(1, 0, 1970)
dbg('text: "%s"' % text) ## dbg('text: "%s"' % text)
converter.ParseTime(text.strip()) converter.ParseTime(text.strip())
currenthour = converter.GetHour() currenthour = converter.GetHour()
dbg('current hour:', currenthour) ## dbg('current hour:', currenthour)
newhour = (currenthour + increment) % 24 newhour = (currenthour + increment) % 24
dbg('newhour:', newhour) ## dbg('newhour:', newhour)
converter.SetHour(newhour) converter.SetHour(newhour)
dbg('converter.GetHour():', converter.GetHour()) ## dbg('converter.GetHour():', converter.GetHour())
newvalue = converter # take advantage of auto-conversion for am/pm in .SetValue() newvalue = converter # take advantage of auto-conversion for am/pm in .SetValue()
else: # minute or second field; handled the same way: else: # minute or second field; handled the same way:
@ -1202,7 +1204,7 @@ class TimeCtrl(BaseMaskedTextCtrl):
except ValueError: # must not be in bounds: except ValueError: # must not be in bounds:
if not wx.Validator_IsSilent(): if not wx.Validator_IsSilent():
wx.Bell() wx.Bell()
dbg(indent=0) ## dbg(indent=0)
def _toGUI( self, wxdt ): def _toGUI( self, wxdt ):
@ -1227,23 +1229,23 @@ class TimeCtrl(BaseMaskedTextCtrl):
not a valid value for the control as currently specified. not a valid value for the control as currently specified.
It is used by both the SetValue() and the IsValid() methods. It is used by both the SetValue() and the IsValid() methods.
""" """
dbg('TimeCtrl::__validateValue(%s)' % repr(value), indent=1) ## dbg('TimeCtrl::__validateValue(%s)' % repr(value), indent=1)
if not value: if not value:
dbg(indent=0) ## dbg(indent=0)
raise ValueError('%s not a valid time value' % repr(value)) raise ValueError('%s not a valid time value' % repr(value))
valid = True # assume true valid = True # assume true
try: try:
value = self.GetWxDateTime(value) # regularize form; can generate ValueError if problem doing so value = self.GetWxDateTime(value) # regularize form; can generate ValueError if problem doing so
except: except:
dbg('exception occurred', indent=0) ## dbg('exception occurred', indent=0)
raise raise
if self.IsLimited() and not self.IsInBounds(value): if self.IsLimited() and not self.IsInBounds(value):
dbg(indent=0) ## dbg(indent=0)
raise ValueError ( raise ValueError (
'value %s is not within the bounds of the control' % str(value) ) 'value %s is not within the bounds of the control' % str(value) )
dbg(indent=0) ## dbg(indent=0)
return value return value
#---------------------------------------------------------------------------- #----------------------------------------------------------------------------
@ -1278,12 +1280,12 @@ if __name__ == '__main__':
self.Bind(EVT_TIMEUPDATE, self.OnTimeChange, self.tc) self.Bind(EVT_TIMEUPDATE, self.OnTimeChange, self.tc)
def OnTimeChange(self, event): def OnTimeChange(self, event):
dbg('OnTimeChange: value = ', event.GetValue()) ## dbg('OnTimeChange: value = ', event.GetValue())
wxdt = self.tc.GetWxDateTime() wxdt = self.tc.GetWxDateTime()
dbg('wxdt =', wxdt.GetHour(), wxdt.GetMinute(), wxdt.GetSecond()) ## dbg('wxdt =', wxdt.GetHour(), wxdt.GetMinute(), wxdt.GetSecond())
if self.test_mx: if self.test_mx:
mxdt = self.tc.GetMxDateTime() mxdt = self.tc.GetMxDateTime()
dbg('mxdt =', mxdt.hour, mxdt.minute, mxdt.second) ## dbg('mxdt =', mxdt.hour, mxdt.minute, mxdt.second)
class MyApp(wx.App): class MyApp(wx.App):

View File

@ -2,9 +2,21 @@
## backwards compatibility. Some names will also have a 'wx' added on if ## backwards compatibility. Some names will also have a 'wx' added on if
## that is how they used to be named in the old wxPython package. ## that is how they used to be named in the old wxPython package.
import wx.lib.maskedctrl import wx.lib.masked.ctrl
__doc__ = wx.lib.maskedctrl.__doc__ __doc__ = wx.lib.masked.ctrl.__doc__
controlTypes = wx.lib.maskedctrl.controlTypes MASKEDTEXT = wx.lib.masked.ctrl.TEXT
wxMaskedCtrl = wx.lib.maskedctrl.MaskedCtrl MASKEDCOMBO = wx.lib.masked.ctrl.COMBO
IPADDR = wx.lib.masked.ctrl.IPADDR
TIME = wx.lib.masked.ctrl.TIME
NUMBER = wx.lib.masked.ctrl.NUMBER
class controlTypes:
MASKEDTEXT = wx.lib.masked.ctrl.TEXT
MASKEDCOMBO = wx.lib.masked.ctrl.COMBO
IPADDR = wx.lib.masked.ctrl.IPADDR
TIME = wx.lib.masked.ctrl.TIME
NUMBER = wx.lib.masked.ctrl.NUMBER
wxMaskedCtrl = wx.lib.masked.Ctrl

View File

@ -1,16 +1,15 @@
## This file imports items from the wx package into the wxPython package for ## This file imports items from the wx package into the wxPython package for
## backwards compatibility. Some names will also have a 'wx' added on if ## backwards compatibility. Some names will also have a 'wx' added on if
## that is how they used to be named in the old wxPython package. ## that is how they used to be named in the old wxPython package.
import wx.lib.masked
import wx.lib.masked.maskededit
import wx.lib.maskededit __doc__ = wx.lib.masked.maskededit.__doc__
__doc__ = wx.lib.maskededit.__doc__ from wx.lib.masked.maskededit import *
Field = wx.lib.maskededit.Field wxMaskedEditMixin = wx.lib.masked.MaskedEditMixin
test = wx.lib.maskededit.test wxMaskedTextCtrl = wx.lib.masked.TextCtrl
test2 = wx.lib.maskededit.test2 wxMaskedComboBox = wx.lib.masked.ComboBox
wxIpAddrCtrl = wx.lib.maskededit.IpAddrCtrl wxMaskedComboBoxSelectEvent = wx.lib.masked.MaskedComboBoxSelectEvent
wxMaskedComboBox = wx.lib.maskededit.MaskedComboBox wxIpAddrCtrl = wx.lib.masked.IpAddrCtrl
wxMaskedComboBoxSelectEvent = wx.lib.maskededit.MaskedComboBoxSelectEvent
wxMaskedEditMixin = wx.lib.maskededit.MaskedEditMixin
wxMaskedTextCtrl = wx.lib.maskededit.MaskedTextCtrl

View File

@ -2,10 +2,10 @@
## backwards compatibility. Some names will also have a 'wx' added on if ## backwards compatibility. Some names will also have a 'wx' added on if
## that is how they used to be named in the old wxPython package. ## that is how they used to be named in the old wxPython package.
import wx.lib.maskednumctrl import wx.lib.masked.numctrl
__doc__ = wx.lib.maskednumctrl.__doc__ __doc__ = wx.lib.masked.numctrl.__doc__
EVT_MASKEDNUM = wx.lib.maskednumctrl.EVT_MASKEDNUM EVT_MASKEDNUM = wx.lib.masked.numctrl.EVT_NUM
wxMaskedNumCtrl = wx.lib.maskednumctrl.MaskedNumCtrl wxMaskedNumCtrl = wx.lib.masked.numctrl.NumCtrl
wxMaskedNumNumberUpdatedEvent = wx.lib.maskednumctrl.MaskedNumNumberUpdatedEvent wxMaskedNumNumberUpdatedEvent = wx.lib.masked.numctrl.NumberUpdatedEvent

View File

@ -2,10 +2,10 @@
## backwards compatibility. Some names will also have a 'wx' added on if ## backwards compatibility. Some names will also have a 'wx' added on if
## that is how they used to be named in the old wxPython package. ## that is how they used to be named in the old wxPython package.
import wx.lib.timectrl import wx.lib.masked.timectrl
__doc__ = wx.lib.timectrl.__doc__ __doc__ = wx.lib.masked.timectrl.__doc__
EVT_TIMEUPDATE = wx.lib.timectrl.EVT_TIMEUPDATE EVT_TIMEUPDATE = wx.lib.masked.timectrl.EVT_TIMEUPDATE
TimeUpdatedEvent = wx.lib.timectrl.TimeUpdatedEvent TimeUpdatedEvent = wx.lib.masked.timectrl.TimeUpdatedEvent
wxTimeCtrl = wx.lib.timectrl.TimeCtrl wxTimeCtrl = wx.lib.masked.timectrl.TimeCtrl