From c878ceeae8d69f231477ef0f207766093547ab86 Mon Sep 17 00:00:00 2001 From: Robin Dunn Date: Mon, 19 Apr 2004 23:24:37 +0000 Subject: [PATCH] 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 --- wxPython/demo/MaskedEditControls.py | 95 +- wxPython/demo/MaskedNumCtrl.py | 35 +- wxPython/demo/TimeCtrl.py | 52 +- wxPython/docs/CHANGES.txt | 3 + wxPython/docs/MigrationGuide.txt | 13 + wxPython/wx/lib/masked/__init__.py | 20 + wxPython/wx/lib/masked/combobox.py | 540 ++++++++ .../wx/lib/{maskedctrl.py => masked/ctrl.py} | 62 +- wxPython/wx/lib/masked/ipaddrctrl.py | 187 +++ wxPython/wx/lib/{ => masked}/maskededit.py | 1110 ++--------------- .../{maskednumctrl.py => masked/numctrl.py} | 301 ++--- wxPython/wx/lib/masked/textctrl.py | 325 +++++ wxPython/wx/lib/{ => masked}/timectrl.py | 210 ++-- wxPython/wxPython/lib/maskedctrl.py | 20 +- wxPython/wxPython/lib/maskededit.py | 19 +- wxPython/wxPython/lib/maskednumctrl.py | 10 +- wxPython/wxPython/lib/timectrl.py | 10 +- 17 files changed, 1592 insertions(+), 1420 deletions(-) create mode 100644 wxPython/wx/lib/masked/__init__.py create mode 100644 wxPython/wx/lib/masked/combobox.py rename wxPython/wx/lib/{maskedctrl.py => masked/ctrl.py} (57%) create mode 100644 wxPython/wx/lib/masked/ipaddrctrl.py rename wxPython/wx/lib/{ => masked}/maskededit.py (88%) rename wxPython/wx/lib/{maskednumctrl.py => masked/numctrl.py} (88%) create mode 100644 wxPython/wx/lib/masked/textctrl.py rename wxPython/wx/lib/{ => masked}/timectrl.py (91%) diff --git a/wxPython/demo/MaskedEditControls.py b/wxPython/demo/MaskedEditControls.py index c20988837a..b88f53b07e 100644 --- a/wxPython/demo/MaskedEditControls.py +++ b/wxPython/demo/MaskedEditControls.py @@ -4,9 +4,8 @@ import sys import traceback import wx -import wx.lib.maskededit as med -import wx.lib.maskedctrl as mctl -import wx.lib.scrolledpanel as scroll +import wx.lib.masked as masked +import wx.lib.scrolledpanel as scroll class demoMixin: @@ -18,7 +17,7 @@ class demoMixin: mask = wx.StaticText( self, -1, "Mask Value" ) formatcode = wx.StaticText( self, -1, "Format" ) 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)) 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]) ) if control in controls: - newControl = med.MaskedTextCtrl( self, -1, "", + newControl = masked.TextCtrl( self, -1, "", mask = control[1], excludeChars = control[2], formatcodes = control[3], @@ -79,7 +78,7 @@ class demoPage1(scroll.ScrolledPanel, demoMixin): self.editList = [] 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 the format codes. @@ -152,8 +151,8 @@ class demoPage2(scroll.ScrolledPanel, demoMixin): label = wx.StaticText( self, -1, """\ All these controls have been created by passing a single parameter, the autoformat code, -and use the factory class MaskedCtrl with its default controlType. -The maskededit module contains an internal dictionary of types and formats (autoformats). +and use the factory class masked.Ctrl with its default controlType. +The masked package contains an internal dictionary of types and formats (autoformats). 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. """) @@ -163,7 +162,7 @@ Many of these already do complicated validation; To see some examples, try description = wx.StaticText( self, -1, "Description") 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 ) ) 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( 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, autoformat), 0, wx.ALIGN_LEFT ) - grid.Add( mctl.MaskedCtrl( self, -1, "", + grid.Add( masked.Ctrl( self, -1, "", autoformat = autoformat, demo = True, name = autoformat), @@ -197,7 +196,7 @@ class demoPage3(scroll.ScrolledPanel, demoMixin): self.editList = [] 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 has a legal range specified. """) @@ -215,7 +214,7 @@ has a legal range specified. controls = [ #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 (unsigned)\n(1-399)","######", "", 'F_', "", (1,399),'', '1 '), ("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" ) autofmt = wx.StaticText( self, -1, "AutoFormat Code" ) 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 ) ) 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 ) 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 = """\ {0: Field(choices=[ "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, autoformat), 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, fields = fieldsDict, demo = True, @@ -287,12 +286,12 @@ Page Up and Shift-Up arrow will similarly cycle backwards through the list. 0, wx.ALIGN_LEFT ) autoformat = "EXPDATEMMYY" - fieldsDict = {1: med.Field(choices=["03", "04", "05"], choiceRequired=True)} + fieldsDict = {1: masked.Field(choices=["03", "04", "05"], choiceRequired=True)} fieldsLabel = """\ {1: Field(choices=[ "03", "04", "05"], choiceRequired=True)}""" - exp = med.MaskedTextCtrl( self, -1, "", + exp = masked.TextCtrl( self, -1, "", autoformat = autoformat, fields = fieldsDict, 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( exp, 0, wx.ALIGN_LEFT ) - fieldsDict = {0: med.Field(choices=["02134","02155"], choiceRequired=True), - 1: med.Field(choices=["1234", "5678"], choiceRequired=False)} + fieldsDict = {0: masked.Field(choices=["02134","02155"], choiceRequired=True), + 1: masked.Field(choices=["1234", "5678"], choiceRequired=False)} fieldsLabel = """\ {0: Field(choices=["02134","02155"], choiceRequired=True), 1: Field(choices=["1234", "5678"], choiceRequired=False)}""" autoformat = "USZIPPLUS4" - zip = med.MaskedTextCtrl( self, -1, "", + zip = masked.TextCtrl( self, -1, "", autoformat = autoformat, fields = fieldsDict, demo = True, @@ -336,7 +335,7 @@ class demoPage5(scroll.ScrolledPanel, demoMixin): labelMaskedCombos = wx.StaticText( self, -1, """\ -These are some examples of MaskedComboBox:""") +These are some examples of masked.ComboBox:""") labelMaskedCombos.SetForegroundColour( "Blue" ) @@ -344,8 +343,8 @@ These are some examples of MaskedComboBox:""") A state selector; only "legal" values can be entered:""") - statecode = med.MaskedComboBox( self, -1, med.states[0], - choices = med.states, + statecode = masked.ComboBox( self, -1, masked.states[0], + choices = masked.states, autoformat="USSTATE") label_statename = wx.StaticText( self, -1, """\ @@ -353,9 +352,9 @@ A state name selector, with auto-select:""") # Create this one using factory function: - statename = mctl.MaskedCtrl( self, -1, med.state_names[0], - controlType = mctl.controlTypes.MASKEDCOMBO, - choices = med.state_names, + statename = masked.Ctrl( self, -1, masked.state_names[0], + controlType = masked.controlTypes.COMBO, + choices = masked.state_names, autoformat="USSTATENAME", autoSelect=True) statename.SetCtrlParameters(formatcodes = 'F!V_') @@ -363,8 +362,8 @@ with auto-select:""") 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] ] - fieldsDict = {0: med.Field(choices=numerators, choiceRequired=False), - 1: med.Field(choices=denominators, choiceRequired=True)} + fieldsDict = {0: masked.Field(choices=numerators, choiceRequired=False), + 1: masked.Field(choices=denominators, choiceRequired=True)} choices = [] for n in numerators: for d in denominators: @@ -377,8 +376,8 @@ A masked ComboBox for fraction selection. Choices for each side of the fraction can be selected with PageUp/Down:""") - fraction = mctl.MaskedCtrl( self, -1, "", - controlType = mctl.MASKEDCOMBO, + fraction = masked.Ctrl( self, -1, "", + controlType = masked.controlTypes.COMBO, choices = choices, choiceRequired = True, mask = "#/##", @@ -392,7 +391,7 @@ A masked ComboBox to validate text from a list of numeric codes:""") choices = ["91", "136", "305", "4579"] - code = med.MaskedComboBox( self, -1, choices[0], + code = masked.ComboBox( self, -1, choices[0], choices = choices, choiceRequired = True, formatcodes = "F_r", @@ -402,8 +401,8 @@ text from a list of numeric codes:""") Programmatically set choice sets:""") self.list_selector = wx.ComboBox(self, -1, '', choices = ['list1', 'list2', 'list3']) - self.dynamicbox = mctl.MaskedCtrl( self, -1, ' ', - controlType = mctl.controlTypes.MASKEDCOMBO, + self.dynamicbox = masked.Ctrl( self, -1, ' ', + controlType = masked.controlTypes.COMBO, mask = 'XXXX', formatcodes = 'F_', # these are to give dropdown some initial height, @@ -415,23 +414,23 @@ choice sets:""") 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" ) 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:") - ipaddr2 = med.IpAddrCtrl( self, -1, mask=" 10. 1.109.###" ) + ipaddr2 = masked.IpAddrCtrl( self, -1, mask=" 10. 1.109.###" ) label_ipaddr3 = wx.StaticText( self, -1, """\ A control with restricted legal values: 10. (1|2) . (129..255) . (0..255)""") - ipaddr3 = mctl.MaskedCtrl( self, -1, - controlType = mctl.controlTypes.IPADDR, + ipaddr3 = masked.Ctrl( self, -1, + controlType = masked.controlTypes.IPADDR, mask=" 10. #.###.###") 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, """\ -Here are some useful configurations of a MaskedTextCtrl 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!)""") +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 masked.NumCtrl class!)""") labelNumerics.SetForegroundColour( "Blue" ) label_intctrl1 = wx.StaticText( self, -1, """\ An integer entry control with 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, """\ 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, """\ A floating point entry control 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(1, defaultValue='00') # don't allow blank fraction @@ -588,7 +587,7 @@ with right-insert for ordinal:""") formatcodes += 'r' mask = '###' else: - choices = med.states + choices = masked.states mask = 'AA' formatcodes += '!' self.dynamicbox.SetCtrlParameters( mask = mask, @@ -628,15 +627,15 @@ def runTest(frame, nb, log): def RunStandalone(): 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) frame.Show(True) app.MainLoop() #---------------------------------------------------------------------------- - +import wx.lib.masked.maskededit as maskededit overview = """

-""" + med.__doc__ + """
+""" + maskededit.__doc__ + """
 
""" diff --git a/wxPython/demo/MaskedNumCtrl.py b/wxPython/demo/MaskedNumCtrl.py index e0e4077600..6d5bda9937 100644 --- a/wxPython/demo/MaskedNumCtrl.py +++ b/wxPython/demo/MaskedNumCtrl.py @@ -4,8 +4,8 @@ import sys import traceback import wx -import wx.lib.maskededit as me -import wx.lib.maskednumctrl as mnum +from wx.lib import masked + #---------------------------------------------------------------------- class TestPanel( wx.Panel ): @@ -16,40 +16,40 @@ class TestPanel( wx.Panel ): panel = wx.Panel( self, -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. """) header.SetForegroundColour( "Blue" ) intlabel = wx.StaticText( panel, -1, "Integer width:" ) - self.integerwidth = mnum.MaskedNumCtrl( + self.integerwidth = masked.NumCtrl( panel, value=10, integerWidth=2, allowNegative=False ) fraclabel = wx.StaticText( panel, -1, "Fraction width:" ) - self.fractionwidth = mnum.MaskedNumCtrl( - panel, value=0, integerWidth=2, allowNegative=False + self.fractionwidth = masked.NumCtrl( + panel, value=0, integerWidth=2, allowNegative=False ) groupcharlabel = wx.StaticText( panel,-1, "Grouping char:" ) - self.groupchar = me.MaskedTextCtrl( + self.groupchar = masked.TextCtrl( panel, -1, value=',', mask='&', excludeChars = '-()', formatcodes='F', emptyInvalid=True, validRequired=True ) decimalcharlabel = wx.StaticText( panel,-1, "Decimal char:" ) - self.decimalchar = me.MaskedTextCtrl( + self.decimalchar = masked.TextCtrl( panel, -1, value='.', mask='&', excludeChars = '-()', formatcodes='F', emptyInvalid=True, validRequired=True ) self.set_min = wx.CheckBox( panel, -1, "Set minimum value:" ) - # Create this MaskedNumCtrl using factory, to show how: - self.min = mnum.MaskedNumCtrl( panel, integerWidth=5, fractionWidth=2 ) + # Create this masked.NumCtrl using factory, to show how: + self.min = masked.Ctrl( panel, integerWidth=5, fractionWidth=2, controlType=masked.controlTypes.NUMBER ) self.min.Enable( False ) 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 ) @@ -68,7 +68,7 @@ The controls at the top reconfigure the resulting control at the bottom. font.SetWeight(wx.BOLD) 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, """\ Programmatically set the above @@ -141,15 +141,15 @@ value entry ctrl:""") panel.Move( (50,10) ) self.panel = panel - self.Bind(mnum.EVT_MASKEDNUM, self.OnSetIntWidth, self.integerwidth ) - self.Bind(mnum.EVT_MASKEDNUM, self.OnSetFractionWidth, self.fractionwidth ) + self.Bind(masked.EVT_NUM, self.OnSetIntWidth, self.integerwidth ) + self.Bind(masked.EVT_NUM, self.OnSetFractionWidth, self.fractionwidth ) self.Bind(wx.EVT_TEXT, self.OnSetGroupChar, self.groupchar ) 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.OnSetMax, self.set_max ) - self.Bind(mnum.EVT_MASKEDNUM, self.SetTargetMinMax, self.min ) - self.Bind(mnum.EVT_MASKEDNUM, self.SetTargetMinMax, self.max ) + self.Bind(masked.EVT_NUM, self.SetTargetMinMax, self.min ) + 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.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.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 ) @@ -323,6 +323,7 @@ def runTest( frame, nb, log ): return win #---------------------------------------------------------------------- +import wx.lib.masked.numctrl as mnum overview = mnum.__doc__ if __name__ == '__main__': diff --git a/wxPython/demo/TimeCtrl.py b/wxPython/demo/TimeCtrl.py index 3f7098d0ed..7b730fb0b9 100644 --- a/wxPython/demo/TimeCtrl.py +++ b/wxPython/demo/TimeCtrl.py @@ -1,12 +1,12 @@ -# +# # 11/21/2003 - Jeff Grimmett (grimmtooth@softhome.net) # # o presense of spin control causing probs (see spin ctrl demo for details) -# +# -import wx -import wx.lib.timectrl as timectl -import wx.lib.scrolledpanel as scrolled +import wx +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:") - 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 ) self.time12.BindSpinButton( spin1 ) text2 = wx.StaticText( self, -1, "24-hour format:") spin2 = wx.SpinButton( self, -1, wx.DefaultPosition, (-1,20), 0 ) - self.time24 = timectl.TimeCtrl( - self, -1, name="24 hour control", fmt24hr=True, - spinButton = spin2 + self.time24 = masked.TimeCtrl( + self, -1, name="24 hour control", fmt24hr=True, + spinButton = spin2 ) text3 = wx.StaticText( self, -1, "No seconds\nor spin button:") - self.spinless_ctrl = timectl.TimeCtrl( - self, -1, name="spinless control", - display_seconds = False + self.spinless_ctrl = masked.TimeCtrl( + self, -1, name="spinless control", + display_seconds = False ) grid = wx.FlexGridSizer( 0, 2, 10, 5 ) @@ -54,8 +54,8 @@ class TestPanel( scrolled.ScrolledPanel ): buttonChange = wx.Button( self, -1, "Change Controls") self.radio12to24 = wx.RadioButton( - self, -1, "Copy 12-hour time to 24-hour control", - wx.DefaultPosition, wx.DefaultSize, wx.RB_GROUP + self, -1, "Copy 12-hour time to 24-hour control", + wx.DefaultPosition, wx.DefaultSize, wx.RB_GROUP ) self.radio24to12 = wx.RadioButton( @@ -86,17 +86,17 @@ class TestPanel( scrolled.ScrolledPanel ): self.set_bounds = wx.CheckBox( self, -1, "Set time bounds:" ) 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 ) 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.limit_check = wx.CheckBox( self, -1, "Limit 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.Add( (20, 0), 0, wx.ALIGN_LEFT|wx.ALL, 5 ) @@ -142,14 +142,14 @@ class TestPanel( scrolled.ScrolledPanel ): self.SetupScrolling() self.Bind(wx.EVT_BUTTON, self.OnButtonClick, buttonChange ) - self.Bind(timectl.EVT_TIMEUPDATE, self.OnTimeChange, self.time12 ) - self.Bind(timectl.EVT_TIMEUPDATE, self.OnTimeChange, self.time24 ) - self.Bind(timectl.EVT_TIMEUPDATE, self.OnTimeChange, self.spinless_ctrl ) + self.Bind(masked.EVT_TIMEUPDATE, self.OnTimeChange, self.time12 ) + self.Bind(masked.EVT_TIMEUPDATE, self.OnTimeChange, self.time24 ) + 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.SetTargetMinMax, self.limit_check ) - self.Bind(timectl.EVT_TIMEUPDATE, self.SetTargetMinMax, self.min ) - self.Bind(timectl.EVT_TIMEUPDATE, self.SetTargetMinMax, self.max ) - self.Bind(timectl.EVT_TIMEUPDATE, self.OnTimeChange, self.target_ctrl ) + self.Bind(masked.EVT_TIMEUPDATE, self.SetTargetMinMax, self.min ) + self.Bind(masked.EVT_TIMEUPDATE, self.SetTargetMinMax, self.max ) + self.Bind(masked.EVT_TIMEUPDATE, self.OnTimeChange, self.target_ctrl ) def OnTimeChange( self, event ): @@ -204,7 +204,7 @@ class TestPanel( scrolled.ScrolledPanel ): min, max = None, None cur_min, cur_max = self.target_ctrl.GetBounds() - + print cur_min, min if min and (min != cur_min): self.target_ctrl.SetMin( min ) if max and (max != cur_max): self.target_ctrl.SetMax( max ) @@ -225,11 +225,11 @@ def runTest( frame, nb, log ): return win #---------------------------------------------------------------------- - +import wx.lib.masked.timectrl as timectl overview = timectl.__doc__ if __name__ == '__main__': import sys,os import run - run.main(['', os.path.basename(sys.argv[0])] + sys.argv[1:]) + run.main(['', os.path.basename(sys.argv[0])]) diff --git a/wxPython/docs/CHANGES.txt b/wxPython/docs/CHANGES.txt index af1127d3e8..dcdf99190b 100644 --- a/wxPython/docs/CHANGES.txt +++ b/wxPython/docs/CHANGES.txt @@ -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) 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. + diff --git a/wxPython/docs/MigrationGuide.txt b/wxPython/docs/MigrationGuide.txt index bf4e356de2..3828d4dc72 100644 --- a/wxPython/docs/MigrationGuide.txt +++ b/wxPython/docs/MigrationGuide.txt @@ -619,6 +619,14 @@ Similarly, the wxSystemSettings backwards compatibiility aliases for GetSystemColour, GetSystemFont and GetSystemMetric have also gone into 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 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 convert itself to one. A similar conversion fragment is in place for 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. + diff --git a/wxPython/wx/lib/masked/__init__.py b/wxPython/wx/lib/masked/__init__.py new file mode 100644 index 0000000000..1a5a59ea70 --- /dev/null +++ b/wxPython/wx/lib/masked/__init__.py @@ -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 diff --git a/wxPython/wx/lib/masked/combobox.py b/wxPython/wx/lib/masked/combobox.py new file mode 100644 index 0000000000..3619c398f2 --- /dev/null +++ b/wxPython/wx/lib/masked/combobox.py @@ -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 "" % 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 + + diff --git a/wxPython/wx/lib/maskedctrl.py b/wxPython/wx/lib/masked/ctrl.py similarity index 57% rename from wxPython/wx/lib/maskedctrl.py rename to wxPython/wx/lib/masked/ctrl.py index d5537775ae..897d0663bd 100644 --- a/wxPython/wx/lib/maskedctrl.py +++ b/wxPython/wx/lib/masked/ctrl.py @@ -1,5 +1,5 @@ #---------------------------------------------------------------------------- -# Name: wxPython.lib.maskedctrl.py +# Name: wxPython.lib.masked.ctrl.py # Author: Will Sadkin # Created: 09/24/2003 # Copyright: (c) 2003 by Will Sadkin @@ -9,32 +9,32 @@ # 12/09/2003 - Jeff Grimmett (grimmtooth@softhome.net) # # o Updated for wx namespace (minor) -# +# # 12/20/2003 - Jeff Grimmett (grimmtooth@softhome.net) # # o Removed wx prefix -# +# """

-MaskedCtrl is actually a factory function for several types of +masked.Ctrl is actually a factory function for several types of masked edit controls:

-MaskedCtrl works by looking for a special controlType +masked.Ctrl works by looking for a special controlType parameter in the variable arguments of the control, to determine what kind of instance to return. controlType can be one of:


-    controlTypes.MASKEDTEXT
-    controlTypes.MASKEDCOMBO
+    controlTypes.TEXT
+    controlTypes.COMBO
     controlTypes.IPADDR
     controlTypes.TIME
     controlTypes.NUMBER
@@ -42,56 +42,56 @@ controlType can be one of:
 These constants are also available individually, ie, you can
 use either of the following:
 

-    from wxPython.wx.lib.maskedctrl import MaskedCtrl, MASKEDCOMBO, MASKEDTEXT, NUMBER
-    from wxPython.wx.lib.maskedctrl import MaskedCtrl, controlTypes
+    from wxPython.wx.lib.masked import Ctrl, COMBO, TEXT, NUMBER, TIME
+    from wxPython.wx.lib.masked import Ctrl, controlTypes
 
If not specified as a keyword argument, the default controlType is -controlTypes.MASKEDTEXT. +controlTypes.TEXT.

Each of the above classes has its own unique arguments, but MaskedCtrl -provides a single "unified" interface for masked controls. MaskedTextCtrl, -MaskedComboBox and IpAddrCtrl are all documented below; the others have +provides a single "unified" interface for masked controls. Masked.TextCtrl, +masked.ComboBox and masked.IpAddrCtrl are all documented below; the others have their own demo pages and interface descriptions. """ -from wx.lib.maskededit import MaskedTextCtrl, MaskedComboBox, IpAddrCtrl -from wx.lib.maskednumctrl import MaskedNumCtrl -from wx.lib.timectrl import TimeCtrl +from wx.lib.masked import TextCtrl, ComboBox, IpAddrCtrl +from wx.lib.masked import NumCtrl +from wx.lib.masked import TimeCtrl # "type" enumeration for class instance factory function -MASKEDTEXT = 0 -MASKEDCOMBO = 1 +TEXT = 0 +COMBO = 1 IPADDR = 2 TIME = 3 NUMBER = 4 # for ease of import class controlTypes: - MASKEDTEXT = MASKEDTEXT - MASKEDCOMBO = MASKEDCOMBO + TEXT = TEXT + COMBO = COMBO IPADDR = IPADDR TIME = TIME NUMBER = NUMBER -def MaskedCtrl( *args, **kwargs): +def Ctrl( *args, **kwargs): """ Actually a factory function providing a unifying interface for generating masked controls. """ if not kwargs.has_key('controlType'): - controlType = MASKEDTEXT + controlType = TEXT else: controlType = kwargs['controlType'] del kwargs['controlType'] - if controlType == MASKEDTEXT: - return MaskedTextCtrl(*args, **kwargs) + if controlType == TEXT: + return TextCtrl(*args, **kwargs) - elif controlType == MASKEDCOMBO: - return MaskedComboBox(*args, **kwargs) + elif controlType == COMBO: + return ComboBox(*args, **kwargs) elif controlType == IPADDR: return IpAddrCtrl(*args, **kwargs) @@ -100,7 +100,7 @@ def MaskedCtrl( *args, **kwargs): return TimeCtrl(*args, **kwargs) elif controlType == NUMBER: - return MaskedNumCtrl(*args, **kwargs) + return NumCtrl(*args, **kwargs) else: raise AttributeError( diff --git a/wxPython/wx/lib/masked/ipaddrctrl.py b/wxPython/wx/lib/masked/ipaddrctrl.py new file mode 100644 index 0000000000..9fea97faa6 --- /dev/null +++ b/wxPython/wx/lib/masked/ipaddrctrl.py @@ -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) + + diff --git a/wxPython/wx/lib/maskededit.py b/wxPython/wx/lib/masked/maskededit.py similarity index 88% rename from wxPython/wx/lib/maskededit.py rename to wxPython/wx/lib/masked/maskededit.py index bdc463543e..1c528c9c66 100644 --- a/wxPython/wx/lib/maskededit.py +++ b/wxPython/wx/lib/masked/maskededit.py @@ -6,7 +6,7 @@ # Copyright: (c) 2003 by Jeff Childers, Will Sadkin, 2003 # Portions: (c) 2002 by Will Sadkin, 2002-2003 # RCS-ID: $Id$ -# License: wxWidgets license +# License: wxWindows license #---------------------------------------------------------------------------- # NOTE: # MaskedEdit controls are based on a suggestion made on [wxPython-Users] by @@ -22,18 +22,28 @@ # #---------------------------------------------------------------------------- # +# This file now contains the bulk of the logic behind all masked controls, +# the MaskedEditMixin class, the Field class, and the autoformat codes. +# +#---------------------------------------------------------------------------- +# +# 03/30/2004 - Will Sadkin (wsadkin@nameconnector.com) +# +# o Split out TextCtrl, ComboBox and IpAddrCtrl into their own files, +# o Reorganized code into masked package +# # 12/09/2003 - Jeff Grimmett (grimmtooth@softhome.net) # # o Updated for wx namespace. No guarantees. This is one huge file. -# +# # 12/13/2003 - Jeff Grimmett (grimmtooth@softhome.net) # # o Missed wx.DateTime stuff earlier. -# +# # 12/20/2003 - Jeff Grimmett (grimmtooth@softhome.net) # -# o wxMaskedEditMixin -> MaskedEditMixin -# o wxMaskedTextCtrl -> MaskedTextCtrl +# o MaskedEditMixin -> MaskedEditMixin +# o wxMaskedTextCtrl -> maskedTextCtrl # o wxMaskedComboBoxSelectEvent -> MaskedComboBoxSelectEvent # o wxMaskedComboBox -> MaskedComboBox # o wxIpAddrCtrl -> IpAddrCtrl @@ -43,38 +53,38 @@ """\ Masked Edit Overview: ===================== -MaskedTextCtrl +masked.TextCtrl is a sublassed text control that can carefully control the user's input based on a mask string you provide. General usage example: - control = MaskedTextCtrl( win, -1, '', mask = '(###) ###-####') + control = masked.TextCtrl( win, -1, '', mask = '(###) ###-####') The example above will create a text control that allows only numbers to be entered and then only in the positions indicated in the mask by the # sign. -MaskedComboBox +masked.ComboBox is a similar subclass of wxComboBox that allows the same sort of masking, but also can do auto-complete of values, and can require the value typed to be in the list of choices to be colored appropriately. -wxMaskedCtrl +masked.Ctrl is actually a factory function for several types of masked edit controls: - MaskedTextCtrl - standard masked edit text box - MaskedComboBox - adds combobox capabilities - IpAddrCtrl - adds special semantics for IP address entry - TimeCtrl - special subclass handling lots of types as values - wxMaskedNumCtrl - special subclass handling numeric values + masked.TextCtrl - standard masked edit text box + masked.ComboBox - adds combobox capabilities + masked.IpAddrCtrl - adds special semantics for IP address entry + masked.TimeCtrl - special subclass handling lots of types as values + masked.NumCtrl - special subclass handling numeric values It works by looking for a controlType parameter in the keyword arguments of the control, to determine what kind of instance to return. If not specified as a keyword argument, the default control type returned - will be MaskedTextCtrl. + will be masked.TextCtrl. - Each of the above classes has its own set of arguments, but wxMaskedCtrl + Each of the above classes has its own set of arguments, but masked.Ctrl provides a single "unified" interface for masked controls. Those for - MaskedTextCtrl, MaskedComboBox and IpAddrCtrl are all documented + masked.TextCtrl, masked.ComboBox and masked.IpAddrCtrl are all documented below; the others have their own demo pages and interface descriptions. (See end of following discussion for how to configure the wxMaskedCtrl() to select the above control types.) @@ -258,7 +268,7 @@ decimalChar= choices= A list of strings that are allowed choices for the control. choiceRequired= value must be member of choices list compareNoCase= Perform case-insensitive matching when validating against list - Note: for MaskedComboBox, this defaults to True. + Note: for masked.ComboBox, this defaults to True. emptyInvalid= Boolean indicating whether an empty value should be considered invalid validFunc= A function to call of the form: bool = func(candidate_value) @@ -412,7 +422,7 @@ decimalChar= after construction; it takes a list of key/value pairs as arguments, where the keys can be any of the mask-specific parameters in the constructor. Eg: - ctl = MaskedTextCtrl( self, -1 ) + ctl = masked.TextCtrl( self, -1 ) ctl.SetCtrlParameters( mask='###-####', defaultValue='555-1212', formatcodes='F') @@ -449,7 +459,7 @@ decimal point. Without a decimal (e.g. '######', the control will treat it as an value. With a decimal (e.g. '###.##'), the control will act as a floating point control (i.e. press decimal to 'tab' to the decimal position). Pressing decimal in the integer control truncates the value. However, for a true numeric control, -MaskedNumCtrl provides all this, and true numeric input/output support as well. +masked.NumCtrl provides all this, and true numeric input/output support as well. Check your controls by calling each control's .IsValid() function and the @@ -462,15 +472,15 @@ Take a look at the demo; the zip-code validation succeeds as long as the first five numerals are entered. the last four are optional, but if any are entered, there must be 4 to be valid. -wxMaskedCtrl Configuration +masked.Ctrl Configuration ========================== -wxMaskedCtrl works by looking for a special controlType +masked.Ctrl works by looking for a special controlType parameter in the variable arguments of the control, to determine what kind of instance to return. controlType can be one of: - controlTypes.MASKEDTEXT - controlTypes.MASKEDCOMBO + controlTypes.TEXT + controlTypes.COMBO controlTypes.IPADDR controlTypes.TIME controlTypes.NUMBER @@ -478,8 +488,8 @@ controlType can be one of: These constants are also available individually, ie, you can use either of the following: - from wxPython.wx.lib.maskedctrl import wxMaskedCtrl, controlTypes - from wxPython.wx.lib.maskedctrl import wxMaskedCtrl, MASKEDCOMBO, MASKEDTEXT, NUMBER + from wxPython.wx.lib.masked import MaskedCtrl, controlTypes + from wxPython.wx.lib.masked import MaskedCtrl, COMBO, TEXT, NUMBER, IPADDR If not specified as a keyword argument, the default controlType is controlTypes.TEXT. @@ -498,8 +508,8 @@ Naming Conventions by derived subclasses start with a capital letter. The following methods must be used and/or defined when deriving a control - from wxMaskedEditMixin. NOTE: if deriving from a *masked edit* control - (eg. class IpAddrCtrl(MaskedTextCtrl) ), then this is NOT necessary, + from MaskedEditMixin. NOTE: if deriving from a *masked edit* control + (eg. class IpAddrCtrl(masked.TextCtrl) ), then this is NOT necessary, as it's already been done for you in the base class. ._SetInitialValue() @@ -514,7 +524,7 @@ Naming Conventions ._GetSelection() REQUIRED - Each class derived from wxMaskedEditMixin must define + Each class derived from MaskedEditMixin must define the function for getting the start and end of the current text selection. The reason for this is that not all controls have the same function name for @@ -527,10 +537,10 @@ Naming Conventions ._SetSelection() REQUIRED Similarly to _GetSelection, each class derived from - wxMaskedEditMixin must define the function for setting + MaskedEditMixin must define the function for setting the start and end of the current text selection. - (eg. .SetSelection() for MaskedTextCtrl, and .SetMark() for - MaskedComboBox. + (eg. .SetSelection() for masked.TextCtrl, and .SetMark() for + masked.ComboBox. ._GetInsertionPoint() ._SetInsertionPoint() @@ -538,11 +548,11 @@ Naming Conventions For consistency, and because the mixin shouldn't rely on fixed names for any manipulations it does of any of the base controls, we require each class derived from - wxMaskedEditMixin to define these functions as well. + MaskedEditMixin to define these functions as well. ._GetValue() ._SetValue() REQUIRED - Each class derived from wxMaskedEditMixin must define + Each class derived from MaskedEditMixin must define the functions used to get and set the raw value of the control. This is necessary so that recursion doesn't take place @@ -557,7 +567,7 @@ Naming Conventions .Paste() .Undo() .SetValue() REQUIRED - Each class derived from wxMaskedEditMixin must redefine + Each class derived from MaskedEditMixin must redefine these functions to call the _Cut(), _Paste(), _Undo() and _SetValue() methods, respectively for the control, so as to prevent programmatic corruption of the control's @@ -565,23 +575,23 @@ Naming Conventions mixin cannot itself override a member of a sibling class. ._Refresh() REQUIRED - Each class derived from wxMaskedEditMixin must define + Each class derived from MaskedEditMixin must define the function used to refresh the base control. .Refresh() REQUIRED - Each class derived from wxMaskedEditMixin must redefine + Each class derived from MaskedEditMixin must redefine this function so that it checks the validity of the control (via self._CheckValid) and then refreshes control using the base class method. ._IsEditable() REQUIRED - Each class derived from wxMaskedEditMixin must define + Each class derived from MaskedEditMixin must define the function used to determine if the base control is - editable or not. (For MaskedComboBox, this has to + editable or not. (For masked.ComboBox, this has to be done with code, rather than specifying the proper function in the base control, as there isn't one...) ._CalcSize() REQUIRED - Each class derived from wxMaskedEditMixin must define + Each class derived from MaskedEditMixin must define the function used to determine how wide the control should be given the mask. (The mixin function ._calcSize() provides a baseline estimate.) @@ -589,7 +599,7 @@ Naming Conventions Event Handling -------------- - Event handlers are "chained", and wxMaskedEditMixin usually + Event handlers are "chained", and MaskedEditMixin usually swallows most of the events it sees, thereby preventing any other handlers from firing in the chain. It is therefore required that each class derivation using the mixin to have an option to hook up @@ -610,9 +620,9 @@ Event Handling where setupEventHandling is an argument to its constructor. - These 5 handlers must be "wired up" for the wxMaskedEdit - control to provide default behavior. (The setupEventHandling - is an argument to MaskedTextCtrl and MaskedComboBox, so + These 5 handlers must be "wired up" for the masked edit + controls to provide default behavior. (The setupEventHandling + is an argument to masked.TextCtrl and masked.ComboBox, so that controls derived from *them* may replace one of these handlers if they so choose.) @@ -622,7 +632,7 @@ Event Handling The following routines are available to facilitate changing - the default behavior of wxMaskedEdit controls: + the default behavior of masked edit controls: ._SetKeycodeHandler(keycode, func) ._SetKeyHandler(char, func) @@ -684,7 +694,7 @@ Event Handling by the "cooked" EVT_CHAR routine. ._OnChar(event) This is the main EVT_CHAR handler for the - wxMaskedEditMixin. + MaskedEditMixin. The following routines are used to handle standard actions for control keys: @@ -705,7 +715,7 @@ Event Handling The following routine provides a hook back to any class derivations, so that they can react to parameter changes before any value is set/reset as a result of - those changes. (eg. MaskedComboBox needs to detect when the choices list is + those changes. (eg. masked.ComboBox needs to detect when the choices list is modified, either implicitly or explicitly, so it can reset the base control to have the appropriate choice list *before* the initial value is reset to match.) @@ -720,8 +730,9 @@ Accessor Functions class BaseMaskedTextCtrl( TextCtrl, MaskedEditMixin ): - class MaskedTextCtrl( BaseMaskedTextCtrl, MaskedEditAccessorsMixin ): - class MaskedNumCtrl( BaseMaskedTextCtrl, MaskedNumCtrlAccessorsMixin ): + class TextCtrl( BaseMaskedTextCtrl, MaskedEditAccessorsMixin ): + class ComboBox( BaseMaskedComboBox, MaskedEditAccessorsMixin ): + class NumCtrl( BaseMaskedTextCtrl, MaskedNumCtrlAccessorsMixin ): class IpAddrCtrl( BaseMaskedTextCtrl, IpAddrCtrlAccessorsMixin ): class TimeCtrl( BaseMaskedTextCtrl, TimeCtrlAccessorsMixin ): @@ -743,7 +754,7 @@ import wx # 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 +from wx.tools.dbg import Logger dbg = Logger() ##dbg(enable=0) @@ -760,12 +771,12 @@ WXK_CTRL_X = (ord('X')+1) - ord('A') WXK_CTRL_Z = (ord('Z')+1) - ord('A') nav = ( - wx.WXK_BACK, wx.WXK_LEFT, wx.WXK_RIGHT, wx.WXK_UP, wx.WXK_DOWN, wx.WXK_TAB, + wx.WXK_BACK, wx.WXK_LEFT, wx.WXK_RIGHT, wx.WXK_UP, wx.WXK_DOWN, wx.WXK_TAB, wx.WXK_HOME, wx.WXK_END, wx.WXK_RETURN, wx.WXK_PRIOR, wx.WXK_NEXT ) control = ( - wx.WXK_BACK, wx.WXK_DELETE, WXK_CTRL_A, WXK_CTRL_C, WXK_CTRL_S, WXK_CTRL_V, + wx.WXK_BACK, wx.WXK_DELETE, WXK_CTRL_A, WXK_CTRL_C, WXK_CTRL_S, WXK_CTRL_V, WXK_CTRL_X, WXK_CTRL_Z ) @@ -878,17 +889,17 @@ masktags = { 'validRegex': '^' + months + '-' + days + '-' + '\d{4} ' + hours + ':' + minutes + ':' + seconds + ' (A|P)M', 'description': "US Date + Time\n(w/hypens)" }, - "USDATE24HRTIMEMMDDYYYY/HHMMSS": { + "USDATE24HRTIMEMMDDYYYY/HHMMSS": { 'mask': "##/##/#### ##:##:##", 'formatcodes': 'DF', 'validRegex': '^' + months + '/' + days + '/' + '\d{4} ' + milhours + ':' + minutes + ':' + seconds, - 'description': "US Date + 24Hr (Military) Time" + 'description': "US Date + 24Hr (Military) Time" }, "USDATE24HRTIMEMMDDYYYY-HHMMSS": { 'mask': "##-##-#### ##:##:##", 'formatcodes': 'DF', 'validRegex': '^' + months + '-' + days + '-' + '\d{4} ' + milhours + ':' + minutes + ':' + seconds, - 'description': "US Date + 24Hr Time\n(w/hypens)" + 'description': "US Date + 24Hr Time\n(w/hypens)" }, "USDATETIMEMMDDYYYY/HHMM": { 'mask': "##/##/#### ##:## AM", @@ -901,7 +912,7 @@ masktags = { 'mask': "##/##/#### ##:##", 'formatcodes': 'DF', 'validRegex': '^' + months + '/' + days + '/' + '\d{4} ' + milhours + ':' + minutes, - 'description': "US Date + 24Hr Time\n(without seconds)" + 'description': "US Date + 24Hr Time\n(without seconds)" }, "USDATETIMEMMDDYYYY-HHMM": { 'mask': "##-##-#### ##:## AM", @@ -1589,7 +1600,7 @@ class MaskedEditMixin: 'demo': False} - def __init__(self, name = 'wxMaskedEdit', **kwargs): + def __init__(self, name = 'MaskedEdit', **kwargs): """ This is the "constructor" for setting up the mixin variable parameters for the composite class. """ @@ -1904,7 +1915,9 @@ class MaskedEditMixin: if self._autofit: ## dbg('setting client size to:', self._CalcSize()) - self.SetClientSize(self._CalcSize()) + size = self._CalcSize() + self.SetSizeHints(size) + self.SetClientSize(size) # Set value/type-specific formatting self._applyFormatting() @@ -1979,7 +1992,9 @@ class MaskedEditMixin: self._SetInitialValue() if self._autofit: - self.SetClientSize(self._CalcSize()) + size = self._CalcSize() + self.SetSizeHints(size) + self.SetClientSize(size) # Set value/type-specific formatting self._applyFormatting() @@ -2642,7 +2657,7 @@ class MaskedEditMixin: else: font = self.GetFont() # get size, weight, etc from current font - # Set to teletype font (guaranteed to be mappable to all wxWidgets + # Set to teletype font (guaranteed to be mappable to all wxWindows # platforms: self._font = wx.Font( font.GetPointSize(), wx.TELETYPE, font.GetStyle(), font.GetWeight(), font.GetUnderlined()) @@ -2711,7 +2726,7 @@ class MaskedEditMixin: def _OnChar(self, event): """ - This is the engine of wxMaskedEdit controls. It examines each keystroke, + This is the engine of MaskedEdit controls. It examines each keystroke, decides if it's allowed, where it should go or what action to take. """ ## dbg('MaskedEditMixin::_OnChar', indent=1) @@ -3365,7 +3380,7 @@ class MaskedEditMixin: pass else: ## dbg("shift-ctrl-end; select to end of non-whitespace") - pass + pass wx.CallAfter(self._SetInsertionPoint, pos) wx.CallAfter(self._SetSelection, pos, end) else: @@ -3988,7 +4003,9 @@ class MaskedEditMixin: pass else: # if at start of control, move to right edge - if sel_to == sel_start and self._isTemplateChar(pos) and pos != end: + if (sel_to == sel_start + and (self._isTemplateChar(pos) or (pos == start and len(slice)+ 1 < field_len)) + and pos != end): pos = end # move to right edge ## elif sel_start <= start and sel_to == end: ## # select to right edge of field - 1 (to replace char) @@ -5081,7 +5098,7 @@ class MaskedEditMixin: valid = False if not valid: ## dbg('cannot convert string to valid time') - pass + pass if valid: dbg('valid date') ## dbg(indent=0) return valid @@ -5852,958 +5869,6 @@ class MaskedEditAccessorsMixin: -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 "" % 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 MaskedTextCtrl( 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 - - -## ---------- ---------- ---------- ---------- ---------- ---------- ---------- -## 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 "" % 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 MaskedComboBox( 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 - - -## ---------- ---------- ---------- ---------- ---------- ---------- ---------- - -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) - - ## ---------- ---------- ---------- ---------- ---------- ---------- ---------- ## these are helper subroutines: @@ -6953,7 +6018,7 @@ Try entering nonsensical or partial values in validated fields to see what happe self.label3 = wx.StaticText( self.panel, -1, "Mask Value") self.label4 = wx.StaticText( self.panel, -1, "Format") self.label5 = wx.StaticText( self.panel, -1, "Reg Expr Val. (opt)") - self.label6 = wx.StaticText( self.panel, -1, "wxMaskedEdit Ctrl") + self.label6 = wx.StaticText( self.panel, -1, "MaskedEdit Ctrl") self.label7 = wx.StaticText( self.panel, -1, label2) self.label7.SetForegroundColour("Blue") self.label1.SetForegroundColour("Blue") @@ -7074,7 +6139,7 @@ Try entering nonsensical or partial values in validated fields to see what happe class test2(wx.Frame): def __init__(self, parent, id, caption): - wx.Frame.__init__( self, parent, id, "wxMaskedEdit control 0.0.7 Demo Page #2 -- AutoFormats", size = (550,600)) + wx.Frame.__init__( self, parent, id, "MaskedEdit control 0.0.7 Demo Page #2 -- AutoFormats", size = (550,600)) from wx.lib.rcsizer import RowColSizer self.panel = wx.Panel( self, -1) self.sizer = RowColSizer() @@ -7090,7 +6155,7 @@ To see a great example of validations in action, try entering a bad email addres self.label1 = wx.StaticText( self.panel, -1, label) self.label2 = wx.StaticText( self.panel, -1, "Description") self.label3 = wx.StaticText( self.panel, -1, "AutoFormat Code") - self.label4 = wx.StaticText( self.panel, -1, "wxMaskedEdit Control") + self.label4 = wx.StaticText( self.panel, -1, "MaskedEdit Control") self.label1.SetForegroundColour("Blue") self.label2.SetFont(wx.Font(9,wx.SWISS,wx.NORMAL,wx.BOLD)) self.label3.SetFont(wx.Font(9,wx.SWISS,wx.NORMAL,wx.BOLD)) @@ -7199,7 +6264,7 @@ i=1 ## ## 3. WS: Controls on wxPanels don't seem to pass Shift-WXK_TAB to their ## EVT_KEY_DOWN or EVT_CHAR event handlers. Until this is fixed in -## wxWidgets, shift-tab won't take you backwards through the fields of +## wxWindows, shift-tab won't take you backwards through the fields of ## a MaskedTextCtrl like it should. Until then Shifted arrow keys will ## work like shift-tab and tab ought to. ## @@ -7219,6 +6284,9 @@ i=1 ## CHANGELOG: ## ==================== +## Version 1.6 +## 1. Reorganized masked controls into separate package, renamed things accordingly +## 2. Split actual controls out of this file into their own files. ## Version 1.5 ## (Reported) bugs fixed: ## 1. Crash ensues if you attempt to change the mask of a read-only @@ -7520,7 +6588,7 @@ i=1 ## (i.e. disallow empty values if True). ## ## Version 0.0.5 -## 1. get_plainValue method renamed to GetPlainValue following the wxWidgets +## 1. get_plainValue method renamed to GetPlainValue following the wxWindows ## StudlyCaps(tm) standard (thanks Paul Moore). ;) ## 2. New format code 'F' causes the control to auto-fit (auto-size) itself ## based on the length of the mask template. diff --git a/wxPython/wx/lib/maskednumctrl.py b/wxPython/wx/lib/masked/numctrl.py similarity index 88% rename from wxPython/wx/lib/maskednumctrl.py rename to wxPython/wx/lib/masked/numctrl.py index 9a3d234793..17c95888d7 100644 --- a/wxPython/wx/lib/maskednumctrl.py +++ b/wxPython/wx/lib/masked/numctrl.py @@ -1,5 +1,5 @@ #---------------------------------------------------------------------------- -# Name: wxPython.lib.maskednumctrl.py +# Name: wxPython.lib.masked.numctrl.py # Author: Will Sadkin # Created: 09/06/2003 # Copyright: (c) 2003 by Will Sadkin @@ -9,12 +9,12 @@ # NOTE: # This was written to provide a numeric edit control for wxPython that # does things like right-insert (like a calculator), and does grouping, etc. -# (ie. the features of MaskedTextCtrl), but allows Get/Set of numeric +# (ie. the features of masked.TextCtrl), but allows Get/Set of numeric # 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 -# 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. # # 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 # 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 # 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 # of values to comply with a fixed-width entry mask. #---------------------------------------------------------------------------- # 12/09/2003 - Jeff Grimmett (grimmtooth@softhome.net) # # o Updated for wx namespace -# +# # 12/20/2003 - Jeff Grimmett (grimmtooth@softhome.net) # # o wxMaskedEditMixin -> MaskedEditMixin -# o wxMaskedTextCtrl -> MaskedTextCtrl -# o wxMaskedNumNumberUpdatedEvent -> MaskedNumNumberUpdatedEvent -# o wxMaskedNumCtrl -> MaskedNumCtrl +# o wxMaskedTextCtrl -> masked.TextCtrl +# o wxMaskedNumNumberUpdatedEvent -> masked.NumberUpdatedEvent +# o wxMaskedNumCtrl -> masked.NumCtrl # """

-MaskedNumCtrl: +masked.NumCtrl:

  • allows you to get and set integer or floating point numbers as value,
  • provides bounds support and optional value limiting,
  • @@ -62,14 +62,14 @@ fractional portion.

    Here's the API:

    -    MaskedNumCtrl(
    +    masked.NumCtrl(
              parent, id = -1,
              value = 0,
              pos = wx.DefaultPosition,
              size = wx.DefaultSize,
              style = 0,
              validator = wx.DefaultValidator,
    -         name = "maskednumber",
    +         name = "masked.number",
              integerWidth = 10,
              fractionWidth = 0,
              allowNone = False,
    @@ -87,7 +87,7 @@ Here's the API:
              emptyBackgroundColour = "White",
              validBackgroundColour = "White",
              invalidBackgroundColour = "Yellow",
    -         autoSize = True         
    +         autoSize = True
              )
     
      @@ -190,7 +190,7 @@ Here's the API:


    -
    EVT_MASKEDNUM(win, id, func) +
    masked.EVT_NUM(win, id, func)
    Respond to a EVT_COMMAND_MASKED_NUMBER_UPDATED event, generated when the value changes. Notice that this event will always be sent when the control's contents changes - whether this is due to user input or @@ -384,18 +384,18 @@ MAXINT = maxint # (constants should be in upper case) MININT = -maxint-1 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(enable=0) +##dbg(enable=0) #---------------------------------------------------------------------------- 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): wx.PyCommandEvent.__init__(self, wxEVT_COMMAND_MASKED_NUMBER_UPDATED, id) @@ -409,8 +409,8 @@ class MaskedNumNumberUpdatedEvent(wx.PyCommandEvent): #---------------------------------------------------------------------------- -class MaskedNumCtrlAccessorsMixin: - # Define wxMaskedNumCtrl's list of attributes having their own +class NumCtrlAccessorsMixin: + # Define masked.NumCtrl's list of attributes having their own # Get/Set functions, ignoring those that make no sense for # an numeric control. exposed_basectrl_params = ( @@ -447,8 +447,8 @@ class MaskedNumCtrlAccessorsMixin: #---------------------------------------------------------------------------- - -class MaskedNumCtrl(BaseMaskedTextCtrl, MaskedNumCtrlAccessorsMixin): + +class NumCtrl(BaseMaskedTextCtrl, NumCtrlAccessorsMixin): valid_ctrl_params = { @@ -470,7 +470,7 @@ class MaskedNumCtrl(BaseMaskedTextCtrl, MaskedNumCtrlAccessorsMixin): 'validBackgroundColour': "White", 'invalidBackgroundColour': "Yellow", 'useFixedWidthFont': True, # by default, use a fixed-width font - 'autoSize': True, # by default, set the width of the control based on the mask + '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, pos = wx.DefaultPosition, size = wx.DefaultSize, style = wx.TE_PROCESS_TAB, validator = wx.DefaultValidator, - name = "maskednum", + name = "masked.num", **kwargs ): - dbg('MaskedNumCtrl::__init__', indent=1) +## dbg('masked.NumCtrl::__init__', indent=1) # Set defaults for control: - dbg('setting defaults:') - for key, param_value in MaskedNumCtrl.valid_ctrl_params.items(): +## dbg('setting defaults:') + for key, param_value in NumCtrl.valid_ctrl_params.items(): # This is done this way to make setattr behave consistently with # "private attribute" name mangling setattr(self, '_' + key, copy.copy(param_value)) # Assign defaults for all attributes: - init_args = copy.deepcopy(MaskedNumCtrl.valid_ctrl_params) - dbg('kwargs:', kwargs) + init_args = copy.deepcopy(NumCtrl.valid_ctrl_params) +## dbg('kwargs:', kwargs) for key, param_value in kwargs.items(): 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) else: init_args[key] = param_value - dbg('init_args:', indent=1) +## dbg('init_args:', indent=1) for key, param_value in init_args.items(): - dbg('%s:' % key, param_value) - dbg(indent=0) +## dbg('%s:' % key, param_value) + pass +## dbg(indent=0) # Process initial fields for the control, as part of construction: if type(init_args['integerWidth']) != types.IntType: @@ -521,7 +522,7 @@ class MaskedNumCtrl(BaseMaskedTextCtrl, MaskedNumCtrlAccessorsMixin): if self._fractionWidth: fracmask = '.' + '#{%d}' % self._fractionWidth - dbg('fracmask:', fracmask) +## dbg('fracmask:', fracmask) fields[1] = Field(defaultValue='0'*self._fractionWidth) else: fracmask = '' @@ -537,7 +538,7 @@ class MaskedNumCtrl(BaseMaskedTextCtrl, MaskedNumCtrlAccessorsMixin): else: emptyInvalid = True fields[0] = Field(formatcodes='r<>', emptyInvalid=emptyInvalid) - dbg('intmask:', intmask) +## dbg('intmask:', intmask) # don't bother to reprocess these arguments: del init_args['integerWidth'] @@ -585,14 +586,14 @@ class MaskedNumCtrl(BaseMaskedTextCtrl, MaskedNumCtrlAccessorsMixin): # Ensure proper coloring: self.Refresh() - dbg('finished MaskedNumCtrl::__init__', indent=0) +## dbg('finished NumCtrl::__init__', indent=0) def SetParameters(self, **kwargs): """ This routine is used to initialize and reconfigure the control: """ - dbg('MaskedNumCtrl::SetParameters', indent=1) +## dbg('NumCtrl::SetParameters', indent=1) maskededit_kwargs = {} reset_fraction_width = False @@ -620,14 +621,14 @@ class MaskedNumCtrl(BaseMaskedTextCtrl, MaskedNumCtrlAccessorsMixin): else: emptyInvalid = True fracmask = '' - dbg('fracmask:', fracmask) +## dbg('fracmask:', fracmask) if kwargs.has_key('integerWidth'): if type(kwargs['integerWidth']) != types.IntType: - dbg(indent=0) +## dbg(indent=0) raise AttributeError('invalid integerWidth (%s) specified; expected integer' % repr(kwargs['integerWidth'])) elif kwargs['integerWidth'] < 0: - dbg(indent=0) +## dbg(indent=0) raise AttributeError('invalid integerWidth (%s) specified; must be > 0' % repr(kwargs['integerWidth'])) else: self._integerWidth = kwargs['integerWidth'] @@ -641,7 +642,7 @@ class MaskedNumCtrl(BaseMaskedTextCtrl, MaskedNumCtrlAccessorsMixin): self._groupSpace = 0 intmask = '#{%d}' % (self._integerWidth + self._groupSpace) - dbg('intmask:', intmask) +## dbg('intmask:', intmask) fields[0] = Field(formatcodes='r<>', emptyInvalid=emptyInvalid) maskededit_kwargs['fields'] = fields @@ -655,17 +656,17 @@ class MaskedNumCtrl(BaseMaskedTextCtrl, MaskedNumCtrlAccessorsMixin): if kwargs.has_key('groupChar'): 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'] if kwargs.has_key('decimalChar'): old_decimalchar = self._decimalChar - dbg("old_decimalchar: '%s'" % old_decimalchar) +## dbg("old_decimalchar: '%s'" % old_decimalchar) maskededit_kwargs['decimalChar'] = kwargs['decimalChar'] # for all other parameters, assign keyword args as appropriate: for key, param_value in kwargs.items(): key = key.replace('Color', 'Colour') - if key not in MaskedNumCtrl.valid_ctrl_params.keys(): + if key not in NumCtrl.valid_ctrl_params.keys(): raise AttributeError('invalid keyword argument "%s"' % key) elif key not in MaskedEditMixin.valid_ctrl_params.keys(): setattr(self, '_' + key, param_value) @@ -673,10 +674,10 @@ class MaskedNumCtrl(BaseMaskedTextCtrl, MaskedNumCtrlAccessorsMixin): raise AttributeError('invalid keyword argument "%s"' % key) else: maskededit_kwargs[key] = param_value - dbg('kwargs:', kwargs) +## dbg('kwargs:', kwargs) # reprocess existing format codes to ensure proper resulting format: - formatcodes = self.GetCtrlParameter('formatcodes') + formatcodes = self.GetCtrlParameter('formatcodes') if kwargs.has_key('allowNegative'): if kwargs['allowNegative'] and '-' not in formatcodes: formatcodes += '-' @@ -695,7 +696,7 @@ class MaskedNumCtrl(BaseMaskedTextCtrl, MaskedNumCtrlAccessorsMixin): if kwargs.has_key('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: formatcodes += 'S' maskededit_kwargs['formatcodes'] = formatcodes @@ -728,7 +729,7 @@ class MaskedNumCtrl(BaseMaskedTextCtrl, MaskedNumCtrlAccessorsMixin): maskededit_kwargs['validRequired'] = False self._limited = kwargs['limited'] - dbg('maskededit_kwargs:', maskededit_kwargs) +## dbg('maskededit_kwargs:', maskededit_kwargs) if maskededit_kwargs.keys(): self.SetCtrlParameters(**maskededit_kwargs) @@ -756,17 +757,18 @@ class MaskedNumCtrl(BaseMaskedTextCtrl, MaskedNumCtrlAccessorsMixin): if( self._max is None or min is None or (self._max is not None and self._max >= min) ): - dbg('examining min') +## dbg('examining min') if min is not None: try: textmin = self._toGUI(min, apply_limits = False) except ValueError: - dbg('min will not fit into control; ignoring', indent=0) +## dbg('min will not fit into control; ignoring', indent=0) raise - dbg('accepted min') +## dbg('accepted min') self._min = min else: - dbg('ignoring min') +## dbg('ignoring min') + pass if kwargs.has_key('max'): @@ -774,24 +776,25 @@ class MaskedNumCtrl(BaseMaskedTextCtrl, MaskedNumCtrlAccessorsMixin): if( self._min is None or max is None or (self._min is not None and self._min <= max) ): - dbg('examining max') +## dbg('examining max') if max is not None: try: textmax = self._toGUI(max, apply_limits = False) except ValueError: - dbg('max will not fit into control; ignoring', indent=0) +## dbg('max will not fit into control; ignoring', indent=0) raise - dbg('accepted max') +## dbg('accepted max') self._max = max else: - dbg('ignoring max') +## dbg('ignoring max') + pass if kwargs.has_key('allowNegative'): self._allowNegative = kwargs['allowNegative'] # Ensure current value of control obeys any new restrictions imposed: text = self._GetValue() - dbg('text value: "%s"' % text) +## dbg('text value: "%s"' % text) if kwargs.has_key('groupChar') and text.find(old_groupchar) != -1: text = text.replace(old_groupchar, self._groupChar) if kwargs.has_key('decimalChar') and text.find(old_decimalchar) != -1: @@ -801,10 +804,10 @@ class MaskedNumCtrl(BaseMaskedTextCtrl, MaskedNumCtrlAccessorsMixin): value = self.GetValue() - dbg('self._allowNegative?', self._allowNegative) +## dbg('self._allowNegative?', self._allowNegative) if not self._allowNegative and self._isNeg: value = abs(value) - dbg('abs(value):', value) +## dbg('abs(value):', value) self._isNeg = False elif not self._allowNone and BaseMaskedTextCtrl.GetValue(self) == '': @@ -815,19 +818,19 @@ class MaskedNumCtrl(BaseMaskedTextCtrl, MaskedNumCtrlAccessorsMixin): sel_start, sel_to = self.GetSelection() 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)) 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)) else: # reformat current value as appropriate to possibly new conditions - dbg('Reformatting value:', value) +## dbg('Reformatting value:', value) sel_start, sel_to = self.GetSelection() self._SetValue(self._toGUI(value)) 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) def _OnChangeSign(self, event): - dbg('MaskedNumCtrl::_OnChangeSign', indent=1) +## dbg('NumCtrl::_OnChangeSign', indent=1) self._typedSign = True MaskedEditMixin._OnChangeSign(self, event) - dbg(indent=0) +## dbg(indent=0) def _disallowValue(self): - dbg('MaskedNumCtrl::_disallowValue') +## dbg('NumCtrl::_disallowValue') # limited and -1 is out of bounds if self._typedSign: self._isNeg = False if not wx.Validator_IsSilent(): wx.Bell() 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.SetSelection, sel_start, sel_to) @@ -886,19 +889,19 @@ class MaskedNumCtrl(BaseMaskedTextCtrl, MaskedNumCtrlAccessorsMixin): 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 (self._fractionWidth == 0 and value.find(self._decimalChar) != -1) ) : value = self._toGUI(value) numvalue = self._GetNumValue(value) - dbg('cleansed value: "%s"' % numvalue) +## dbg('cleansed value: "%s"' % numvalue) replacement = None if numvalue == "": if self._allowNone: - dbg('calling base BaseMaskedTextCtrl._SetValue(self, "%s")' % value) +## dbg('calling base BaseMaskedTextCtrl._SetValue(self, "%s")' % value) BaseMaskedTextCtrl._SetValue(self, value) self.Refresh() return @@ -906,54 +909,54 @@ class MaskedNumCtrl(BaseMaskedTextCtrl, MaskedNumCtrlAccessorsMixin): replacement = self._min else: replacement = 0 - dbg('empty value; setting replacement:', replacement) +## dbg('empty value; setting replacement:', replacement) if replacement is None: # Go get the integer portion about to be set and verify its validity intstart, intend = self._fields[0]._extent - dbg('intstart, intend:', intstart, intend) - dbg('raw integer:"%s"' % value[intstart:intend]) +## dbg('intstart, intend:', intstart, intend) +## dbg('raw integer:"%s"' % value[intstart:intend]) int = self._GetNumValue(value[intstart:intend]) numval = self._fromGUI(value) - dbg('integer: "%s"' % int) +## dbg('integer: "%s"' % int) try: fracval = self.GetFraction(value) 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() - dbg(indent=0) +## dbg(indent=0) return if fracval == 0.0: - dbg('self._isNeg?', self._isNeg) +## dbg('self._isNeg?', self._isNeg) 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 self._isNeg = False elif int[:2] == '-0' and self._fractionWidth == 0: if self._oldvalue < 0: - dbg('-0; setting replacement of 0') +## dbg('-0; setting replacement of 0') replacement = 0 self._isNeg = False 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 self._isNeg = True else: # limited and -1 is out of bounds self._disallowValue() - dbg(indent=0) +## dbg(indent=0) return 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): - dbg('just a negative sign; setting replacement of -1') +## dbg('just a negative sign; setting replacement of -1') replacement = -1 else: # limited and -1 is out of bounds self._disallowValue() - dbg(indent=0) +## dbg(indent=0) return elif( self._typedSign @@ -963,7 +966,7 @@ class MaskedNumCtrl(BaseMaskedTextCtrl, MaskedNumCtrlAccessorsMixin): # changed sign resulting in value that's now out-of-bounds; # disallow self._disallowValue() - dbg(indent=0) +## dbg(indent=0) return if replacement is None: @@ -974,37 +977,37 @@ class MaskedNumCtrl(BaseMaskedTextCtrl, MaskedNumCtrlAccessorsMixin): # integer requested is not legal. This can happen if the user # is attempting to insert a digit in the middle of the control # 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(): wx.Bell() 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.SetSelection, sel_start, sel_to) - dbg(indent=0) +## dbg(indent=0) return if int[0] == '0' and len(int) > 1: - dbg('numvalue: "%s"' % numvalue.replace(' ', '')) +## dbg('numvalue: "%s"' % numvalue.replace(' ', '')) if self._fractionWidth: value = self._toGUI(string.atof(numvalue)) else: value = self._toGUI(string.atol(numvalue)) - dbg('modified value: "%s"' % value) +## dbg('modified value: "%s"' % value) self._typedSign = False # reset state var if replacement is not None: # Value presented wasn't a legal number, but control should do something # reasonable instead: - dbg('setting replacement value:', replacement) +## dbg('setting replacement value:', 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))) - 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.SetSelection, sel_start, sel_to) - dbg(indent=0) +## dbg(indent=0) return # Otherwise, apply appropriate formatting to value: @@ -1016,35 +1019,35 @@ class MaskedNumCtrl(BaseMaskedTextCtrl, MaskedNumCtrlAccessorsMixin): else: self._isNeg = False - dbg('value:"%s"' % value, 'self._useParens:', self._useParens) +## dbg('value:"%s"' % value, 'self._useParens:', self._useParens) if self._fractionWidth: adjvalue = self._adjustFloat(self._GetNumValue(value).replace('.',self._decimalChar)) else: 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 - dbg('calling BaseMaskedTextCtrl._SetValue(self, "%s")' % adjvalue) +## dbg('calling BaseMaskedTextCtrl._SetValue(self, "%s")' % adjvalue) BaseMaskedTextCtrl._SetValue(self, adjvalue) # After all actions so far scheduled, check that resulting cursor # position is appropriate, and move if not: wx.CallAfter(self._CheckInsertionPoint) - dbg('finished MaskedNumCtrl::_SetValue', indent=0) +## dbg('finished NumCtrl::_SetValue', indent=0) def _CheckInsertionPoint(self): # If current insertion point is before the end of the integer and # its before the 1st digit, place it just after the sign position: - dbg('MaskedNumCtrl::CheckInsertionPoint', indent=1) +## dbg('NumCtrl::CheckInsertionPoint', indent=1) sel_start, sel_to = self._GetSelection() text = self._GetValue() if sel_to < self._fields[0]._extent[1] and text[sel_to] in (' ', '-', '('): 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.SetSelection(signpos+1, signpos+1) - dbg(indent=0) +## dbg(indent=0) def _OnErase( self, event ): @@ -1053,7 +1056,7 @@ class MaskedNumCtrl(BaseMaskedTextCtrl, MaskedNumCtrlAccessorsMixin): grouping characters auto selects the digit before or after the 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 # delete next digit to appropriate side: @@ -1086,21 +1089,21 @@ class MaskedNumCtrl(BaseMaskedTextCtrl, MaskedNumCtrlAccessorsMixin): self.SetSelection(sel_start, sel_to+1) BaseMaskedTextCtrl._OnErase(self, event) - dbg(indent=0) +## dbg(indent=0) def OnTextChange( self, event ): """ 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 contents from within a EVT_CHAR handler can cause double text events. So we check for actual changes to the text before passing the events on. """ - dbg('MaskedNumCtrl::OnTextChange', indent=1) +## dbg('NumCtrl::OnTextChange', indent=1) if not BaseMaskedTextCtrl._OnTextChange(self, event): - dbg(indent=0) +## dbg(indent=0) return # else... legal value @@ -1109,14 +1112,14 @@ class MaskedNumCtrl(BaseMaskedTextCtrl, MaskedNumCtrlAccessorsMixin): if value != self._oldvalue: try: self.GetEventHandler().ProcessEvent( - MaskedNumNumberUpdatedEvent( self.GetId(), self.GetValue(), self ) ) + NumberUpdatedEvent( self.GetId(), self.GetValue(), self ) ) except ValueError: - dbg(indent=0) +## dbg(indent=0) return # let normal processing of the text continue event.Skip() self._oldvalue = value # record for next event - dbg(indent=0) +## dbg(indent=0) def _GetValue(self): """ @@ -1172,7 +1175,7 @@ class MaskedNumCtrl(BaseMaskedTextCtrl, MaskedNumCtrlAccessorsMixin): If min > the max value allowed by the width of the control, the function will return False, and the min will not be set. """ - dbg('MaskedNumCtrl::SetMin(%s)' % repr(min), indent=1) +## dbg('NumCtrl::SetMin(%s)' % repr(min), indent=1) if( self._max is None or min is None or (self._max is not None and self._max >= min) ): @@ -1183,7 +1186,7 @@ class MaskedNumCtrl(BaseMaskedTextCtrl, MaskedNumCtrlAccessorsMixin): bRet = False else: bRet = False - dbg(indent=0) +## dbg(indent=0) return bRet 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 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: value = self.GetValue() else: try: value = self._GetNumValue(self._toGUI(value)) 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 if value.strip() == '': value = None @@ -1309,10 +1312,10 @@ class MaskedNumCtrl(BaseMaskedTextCtrl, MaskedNumCtrlAccessorsMixin): # if bounds set, and value is None, return False 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 else: - dbg('finished IsInBounds', indent=0) +## dbg('finished IsInBounds', indent=0) return min <= value <= max @@ -1383,21 +1386,21 @@ class MaskedNumCtrl(BaseMaskedTextCtrl, MaskedNumCtrlAccessorsMixin): type and bounds checking and raises ValueError if argument is 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(): - dbg(indent=0) +## dbg(indent=0) return self._template elif type(value) in (types.StringType, types.UnicodeType): value = self._GetNumValue(value) - dbg('cleansed num value: "%s"' % value) +## dbg('cleansed num value: "%s"' % value) if value == "": if self.IsNoneAllowed(): - dbg(indent=0) +## dbg(indent=0) return self._template else: - dbg('exception raised:', e, indent=0) - raise ValueError ('wxMaskedNumCtrl requires numeric value, passed %s'% repr(value) ) +## dbg('exception raised:', e, indent=0) + raise ValueError ('NumCtrl requires numeric value, passed %s'% repr(value) ) # else... try: if self._fractionWidth or value.find('.') != -1: @@ -1405,13 +1408,13 @@ class MaskedNumCtrl(BaseMaskedTextCtrl, MaskedNumCtrlAccessorsMixin): else: value = long(value) except Exception, e: - dbg('exception raised:', e, indent=0) - raise ValueError ('MaskedNumCtrl requires numeric value, passed %s'% repr(value) ) +## dbg('exception raised:', e, indent=0) + raise ValueError ('NumCtrl requires numeric value, passed %s'% repr(value) ) elif type(value) not in (types.IntType, types.LongType, types.FloatType): - dbg(indent=0) +## dbg(indent=0) 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: raise ValueError ( @@ -1421,29 +1424,29 @@ class MaskedNumCtrl(BaseMaskedTextCtrl, MaskedNumCtrlAccessorsMixin): min = self.GetMin() max = self.GetMax() if not min is None and value < min: - dbg(indent=0) +## dbg(indent=0) raise ValueError ( 'value %d is below minimum value of control'% value ) if not max is None and value > max: - dbg(indent=0) +## dbg(indent=0) raise ValueError ( 'value %d exceeds value of control'% value ) adjustwidth = len(self._mask) - (1 * self._useParens * self._signOk) - dbg('len(%s):' % self._mask, len(self._mask)) - dbg('adjustwidth - groupSpace:', adjustwidth - self._groupSpace) - dbg('adjustwidth:', adjustwidth) +## dbg('len(%s):' % self._mask, len(self._mask)) +## dbg('adjustwidth - groupSpace:', adjustwidth - self._groupSpace) +## dbg('adjustwidth:', adjustwidth) if self._fractionWidth == 0: s = str(long(value)).rjust(self._integerWidth) else: format = '%' + '%d.%df' % (self._integerWidth+self._fractionWidth+1, self._fractionWidth) 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): - dbg(indent=0) +## dbg(indent=0) 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): - dbg(indent=0) +## dbg(indent=0) raise ValueError ('value %s exceeds the integer width of the control (%d)' % (s, self._integerWidth)) s = s.rjust(adjustwidth).replace('.', self._decimalChar) @@ -1452,7 +1455,7 @@ class MaskedNumCtrl(BaseMaskedTextCtrl, MaskedNumCtrlAccessorsMixin): s = s.replace('-', '(') + ')' else: s += ' ' - dbg('returned: "%s"' % s, indent=0) +## dbg('returned: "%s"' % s, indent=0) return s @@ -1460,8 +1463,8 @@ class MaskedNumCtrl(BaseMaskedTextCtrl, MaskedNumCtrlAccessorsMixin): """ Conversion function used in getting the value of the control. """ - dbg(suspend=0) - dbg('MaskedNumCtrl::_fromGUI(%s)' % value, indent=1) +## dbg(suspend=0) +## dbg('NumCtrl::_fromGUI(%s)' % value, indent=1) # One or more of the underlying text control implementations # issue an intermediate EVT_TEXT when replacing the control's # value, where the intermediate value is an empty string. @@ -1470,42 +1473,42 @@ class MaskedNumCtrl(BaseMaskedTextCtrl, MaskedNumCtrlAccessorsMixin): # if value.strip() == '': 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: return 0.0 else: return 0 else: - dbg('empty value; returning None', indent = 0) +## dbg('empty value; returning None', indent = 0) return None else: value = self._GetNumValue(value) - dbg('Num value: "%s"' % value) +## dbg('Num value: "%s"' % value) if self._fractionWidth: try: - dbg(indent=0) +## dbg(indent=0) return float( value ) except ValueError: - dbg("couldn't convert to float; returning None") +## dbg("couldn't convert to float; returning None") return None else: raise else: try: - dbg(indent=0) +## dbg(indent=0) return int( value ) except ValueError: try: - dbg(indent=0) +## dbg(indent=0) return long( value ) except ValueError: - dbg("couldn't convert to long; returning None") +## dbg("couldn't convert to long; returning None") return None else: raise else: - dbg('exception occurred; returning None') +## dbg('exception occurred; returning None') return None @@ -1514,7 +1517,7 @@ class MaskedNumCtrl(BaseMaskedTextCtrl, MaskedNumCtrlAccessorsMixin): Preprocessor for base control paste; if value needs to be right-justified 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: paste_text = self._getClipboardContents() else: @@ -1545,7 +1548,7 @@ if __name__ == '__main__': style = wx.DEFAULT_DIALOG_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.Cancel = wx.Button( self, wx.ID_CANCEL, "Cancel") @@ -1560,7 +1563,7 @@ if __name__ == '__main__': self.SetSizer( vs ) vs.Fit( 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): print 'value now', event.GetValue() @@ -1578,7 +1581,7 @@ if __name__ == '__main__': return True 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.SetInsertionPoint(1) dlg.int_ctrl.SetSelection(1,2) diff --git a/wxPython/wx/lib/masked/textctrl.py b/wxPython/wx/lib/masked/textctrl.py new file mode 100644 index 0000000000..d73fbe4a4e --- /dev/null +++ b/wxPython/wx/lib/masked/textctrl.py @@ -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 "" % 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 + + diff --git a/wxPython/wx/lib/timectrl.py b/wxPython/wx/lib/masked/timectrl.py similarity index 91% rename from wxPython/wx/lib/timectrl.py rename to wxPython/wx/lib/masked/timectrl.py index af42f0836b..36fbbee97a 100644 --- a/wxPython/wx/lib/timectrl.py +++ b/wxPython/wx/lib/masked/timectrl.py @@ -36,11 +36,11 @@ # 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 # that's at fault. -# +# # 12/20/2003 - Jeff Grimmett (grimmtooth@softhome.net) # -# o wxMaskedTextCtrl -> MaskedTextCtrl -# o wxTimeCtrl -> TimeCtrl +# o wxMaskedTextCtrl -> masked.TextCtrl +# o wxTimeCtrl -> masked.TimeCtrl # """ @@ -65,7 +65,7 @@ Here's the API for TimeCtrl: style = wxTE_PROCESS_TAB, validator = wx.DefaultValidator, name = "time", - format = 'HHMMSS', + format = 'HHMMSS', fmt24hr = False, displaySeconds = True, spinButton = None, @@ -95,7 +95,7 @@ Here's the API for TimeCtrl:
    This parameter can be used instead of the fmt24hr and displaySeconds parameters, respectively; it provides a shorthand way to specify the time format you want. Accepted values are 'HHMMSS', 'HHMM', '24HHMMSS', and - '24HHMM'. If the format is specified, the other two arguments will be ignored. + '24HHMM'. If the format is specified, the other two arguments will be ignored.
    fmt24hr
    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:
    If True, control will include a seconds field; if False, it will just show hours and minutes. (This value is ignored if the format parameter is specified.) -
    +
    spinButton
    If specified, this button's events will be bound to the behavior of the TimeCtrl, working like up/down cursor key events. (See BindSpinButton.) @@ -272,10 +272,10 @@ import types import wx from wx.tools.dbg import Logger -from wx.lib.maskededit import BaseMaskedTextCtrl, Field +from wx.lib.masked import Field, BaseMaskedTextCtrl dbg = Logger() -dbg(enable=0) +##dbg(enable=0) try: from mx import DateTime @@ -322,8 +322,8 @@ class TimeCtrlAccessorsMixin: exec('def Set%s(self, value): self.SetCtrlParameters(%s=value)' % (propname, param)) exec('def Get%s(self): return self.GetCtrlParameter("%s")''' % (propname, param)) - - + + class TimeCtrl(BaseMaskedTextCtrl): valid_ctrl_params = { @@ -333,7 +333,7 @@ class TimeCtrl(BaseMaskedTextCtrl): 'max': None, 'limited': False, # by default, no limiting even if bounds set '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__ ( @@ -347,7 +347,7 @@ class TimeCtrl(BaseMaskedTextCtrl): **kwargs ): # set defaults for control: - dbg('setting defaults:') +## dbg('setting defaults:') for key, param_value in TimeCtrl.valid_ctrl_params.items(): # This is done this way to make setattr behave consistently with # "private attribute" name mangling @@ -456,7 +456,7 @@ class TimeCtrl(BaseMaskedTextCtrl): def SetParameters(self, **kwargs): - dbg('TimeCtrl::SetParameters(%s)' % repr(kwargs), indent=1) +## dbg('TimeCtrl::SetParameters(%s)' % repr(kwargs), indent=1) maskededit_kwargs = {} reset_format = False @@ -553,10 +553,10 @@ class TimeCtrl(BaseMaskedTextCtrl): self.SetValue(value) except: self.SetValue('12:00:00 AM') - dbg(indent=0) +## dbg(indent=0) return {} # no arguments to return else: - dbg(indent=0) +## dbg(indent=0) return maskededit_kwargs @@ -565,7 +565,7 @@ class TimeCtrl(BaseMaskedTextCtrl): This function binds an externally created spin button to the control, so that up/down events from the button automatically change the control. """ - dbg('TimeCtrl::BindSpinButton') +## dbg('TimeCtrl::BindSpinButton') self.__spinButton = sb if self.__spinButton: # bind event handlers to spin ctrl @@ -584,16 +584,16 @@ class TimeCtrl(BaseMaskedTextCtrl): and convert wxDateTime, mxDateTime, or 12/24 format time string 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: strtime = self._toGUI(self.__validateValue(value)) except: - dbg('validation failed', indent=0) +## dbg('validation failed', indent=0) raise - dbg('strtime:', strtime) +## dbg('strtime:', strtime) self._SetValue(strtime) - dbg(indent=0) +## dbg(indent=0) def GetValue(self, as_wxDateTime = False, @@ -641,12 +641,12 @@ class TimeCtrl(BaseMaskedTextCtrl): raised. """ global accept_mx - dbg(suspend=1) - dbg('TimeCtrl::GetWxDateTime(%s)' % repr(value), indent=1) +## dbg(suspend=1) +## dbg('TimeCtrl::GetWxDateTime(%s)' % repr(value), indent=1) if value is None: - dbg('getting control value') +## dbg('getting control value') value = self.GetValue() - dbg('value = "%s"' % value) +## dbg('value = "%s"' % value) if type(value) == types.UnicodeType: value = str(value) # convert to regular string @@ -656,14 +656,14 @@ class TimeCtrl(BaseMaskedTextCtrl): # Construct constant wxDateTime, then try to parse the string: wxdt = wx.DateTimeFromDMY(1, 0, 1970) - dbg('attempting conversion') +## dbg('attempting conversion') value = value.strip() # (parser doesn't like leading spaces) checkTime = wxdt.ParseTime(value) valid = checkTime == len(value) # entire string parsed? - dbg('checkTime == len(value)?', valid) +## dbg('checkTime == len(value)?', valid) if not valid: - dbg(indent=0, suspend=0) +## dbg(indent=0, suspend=0) raise ValueError('cannot convert string "%s" to valid time' % value) else: @@ -685,7 +685,7 @@ class TimeCtrl(BaseMaskedTextCtrl): error = 'GetWxDateTime requires wxDateTime, mxDateTime or parsable time string, passed %s'% repr(value) else: 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) wxdt = wx.DateTimeFromDMY(1, 0, 1970) @@ -693,7 +693,7 @@ class TimeCtrl(BaseMaskedTextCtrl): wxdt.SetMinute(minute) wxdt.SetSecond(second) - dbg('wxdt:', wxdt, indent=0, suspend=0) +## dbg('wxdt:', wxdt, indent=0, suspend=0) return wxdt @@ -730,13 +730,13 @@ class TimeCtrl(BaseMaskedTextCtrl): adjusted to the new minimum value; if not limited, the value in the 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: try: min = self.GetWxDateTime(min) self.__min = self._toGUI(min) except: - dbg('exception occurred', indent=0) +## dbg('exception occurred', indent=0) return False else: self.__min = min @@ -746,7 +746,7 @@ class TimeCtrl(BaseMaskedTextCtrl): else: self._CheckValid() ret = True - dbg('ret:', ret, indent=0) +## dbg('ret:', ret, indent=0) return ret @@ -757,22 +757,23 @@ class TimeCtrl(BaseMaskedTextCtrl): the current minimum bound on the control, as a wxDateTime by default, or as a string if as_string argument is True. """ - dbg(suspend=1) - dbg('TimeCtrl::GetMin, as_string?', as_string, indent=1) +## dbg(suspend=1) +## dbg('TimeCtrl::GetMin, as_string?', as_string, indent=1) if self.__min is None: - dbg('(min == None)') +## dbg('(min == None)') ret = self.__min elif as_string: ret = self.__min - dbg('ret:', ret) +## dbg('ret:', ret) else: try: ret = self.GetWxDateTime(self.__min) except: - dbg(suspend=0) - dbg('exception occurred', indent=0) - dbg('ret:', repr(ret)) - dbg(indent=0, suspend=0) +## dbg(suspend=0) +## dbg('exception occurred', indent=0) + raise +## dbg('ret:', repr(ret)) +## dbg(indent=0, suspend=0) return ret @@ -789,23 +790,23 @@ class TimeCtrl(BaseMaskedTextCtrl): adjusted to this maximum value; if not limited, the value in the 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: try: max = self.GetWxDateTime(max) self.__max = self._toGUI(max) except: - dbg('exception occurred', indent=0) +## dbg('exception occurred', indent=0) return False else: self.__max = max - dbg('max:', repr(self.__max)) +## dbg('max:', repr(self.__max)) if self.IsLimited() and not self.IsInBounds(): self.SetLimited(self.__limited) # force limited value: else: self._CheckValid() ret = True - dbg('ret:', ret, indent=0) +## dbg('ret:', ret, indent=0) return ret @@ -816,23 +817,23 @@ class TimeCtrl(BaseMaskedTextCtrl): the current minimum bound on the control, as a wxDateTime by default, or as a string if as_string argument is True. """ - dbg(suspend=1) - dbg('TimeCtrl::GetMin, as_string?', as_string, indent=1) +## dbg(suspend=1) +## dbg('TimeCtrl::GetMin, as_string?', as_string, indent=1) if self.__max is None: - dbg('(max == None)') +## dbg('(max == None)') ret = self.__max elif as_string: ret = self.__max - dbg('ret:', ret) +## dbg('ret:', ret) else: try: ret = self.GetWxDateTime(self.__max) except: - dbg(suspend=0) - dbg('exception occurred', indent=0) +## dbg(suspend=0) +## dbg('exception occurred', indent=0) raise - dbg('ret:', repr(ret)) - dbg(indent=0, suspend=0) +## dbg('ret:', repr(ret)) +## dbg(indent=0, suspend=0) return ret @@ -868,22 +869,22 @@ class TimeCtrl(BaseMaskedTextCtrl): limiting, but coloring of out-of-bounds values will still take 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 if not limited: self.SetMaskParameters(validRequired = False) self._CheckValid() - dbg(indent=0) +## dbg(indent=0) return - dbg('requiring valid value') +## dbg('requiring valid value') self.SetMaskParameters(validRequired = True) min = self.GetMin() max = self.GetMax() 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 elif not self.IsInBounds(): @@ -891,11 +892,11 @@ class TimeCtrl(BaseMaskedTextCtrl): try: value = self.GetWxDateTime() except: - dbg('exception occurred', indent=0) +## dbg('exception occurred', indent=0) raise if min <= max: # valid range doesn't span midnight - dbg('min <= max') +## dbg('min <= max') # which makes the "nearest bound" computation trickier... # determine how long the "invalid" pie wedge is, and cut @@ -918,24 +919,24 @@ class TimeCtrl(BaseMaskedTextCtrl): cmp_value = value 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) else: - dbg('forcing value to max (%s)' % max.FormatTime()) +## dbg('forcing value to max (%s)' % max.FormatTime()) self.SetValue(max) else: - dbg('max < min') +## dbg('max < min') # therefore max < value < min guaranteed to be true, # so "nearest bound" calculation is much easier: if (value - max) >= (min - value): # 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) else: - dbg('forcing value to max (%s)' % max.FormatTime()) +## dbg('forcing value to max (%s)' % max.FormatTime()) self.SetValue(max) - dbg(indent=0) +## dbg(indent=0) @@ -961,21 +962,22 @@ class TimeCtrl(BaseMaskedTextCtrl): try: value = self.GetWxDateTime(value) # try to regularize passed value except ValueError: - dbg('ValueError getting wxDateTime for %s' % repr(value), indent=0) +## dbg('ValueError getting wxDateTime for %s' % repr(value), indent=0) 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: - dbg(indent=0) +## dbg(indent=0) return True elif value is None: try: value = self.GetWxDateTime() 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: min = self.GetMin() @@ -990,7 +992,7 @@ class TimeCtrl(BaseMaskedTextCtrl): # either "min" <= value (<= midnight of *next day*) # 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 @@ -1021,7 +1023,7 @@ class TimeCtrl(BaseMaskedTextCtrl): def __OnTextChange(self, event=None): - dbg('TimeCtrl::OnTextChange', indent=1) +## dbg('TimeCtrl::OnTextChange', indent=1) # Allow Maskedtext base control to color as appropriate, # and Skip the EVT_TEXT event (if appropriate.) @@ -1035,11 +1037,11 @@ class TimeCtrl(BaseMaskedTextCtrl): if not BaseMaskedTextCtrl._OnTextChange(self, event): return - dbg('firing TimeUpdatedEvent...') +## dbg('firing TimeUpdatedEvent...') evt = TimeUpdatedEvent(self.GetId(), self.GetValue()) evt.SetEventObject(self) self.GetEventHandler().ProcessEvent(evt) - dbg(indent=0) +## dbg(indent=0) def SetInsertionPoint(self, pos): @@ -1048,14 +1050,14 @@ class TimeCtrl(BaseMaskedTextCtrl): This is necessary to handle the optional spin button, because the insertion 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) self.__posCurrent = self.GetInsertionPoint() - dbg(indent=0) +## dbg(indent=0) 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 if sel_start < 0: @@ -1069,7 +1071,7 @@ class TimeCtrl(BaseMaskedTextCtrl): self.__bSelection = sel_start != sel_to BaseMaskedTextCtrl.SetSelection(self, sel_start, sel_to) - dbg(indent=0) +## dbg(indent=0) def __OnSpin(self, key): @@ -1085,7 +1087,7 @@ class TimeCtrl(BaseMaskedTextCtrl): start, end = self._FindField(self.__posCurrent)._extent self.SetInsertionPoint(start) self.SetSelection(start, end) - dbg('current position:', self.__posCurrent) +## dbg('current position:', self.__posCurrent) def __OnSpinUp(self, event): @@ -1093,10 +1095,10 @@ class TimeCtrl(BaseMaskedTextCtrl): Event handler for any bound spin button on EVT_SPIN_UP; 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) keep_processing = False - dbg(indent=0) +## dbg(indent=0) return keep_processing @@ -1105,10 +1107,10 @@ class TimeCtrl(BaseMaskedTextCtrl): Event handler for any bound spin button on EVT_SPIN_DOWN; 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) keep_processing = False - dbg(indent=0) +## dbg(indent=0) return keep_processing @@ -1119,14 +1121,14 @@ class TimeCtrl(BaseMaskedTextCtrl): It then calls the base control's _OnChar routine with the modified event instance. """ - dbg('TimeCtrl::OnChar', indent=1) +## dbg('TimeCtrl::OnChar', indent=1) keycode = event.GetKeyCode() - dbg('keycode:', keycode) +## dbg('keycode:', keycode) if keycode == ord(':'): - dbg('colon seen! removing shift attribute') +## dbg('colon seen! removing shift attribute') event.m_shiftDown = False BaseMaskedTextCtrl._OnChar(self, event ) ## handle each keypress - dbg(indent=0) +## dbg(indent=0) def __OnSetToNow(self, event): @@ -1144,7 +1146,7 @@ class TimeCtrl(BaseMaskedTextCtrl): Event handler for motion events; this handler changes limits the selection to the new cell boundaries. """ - dbg('TimeCtrl::LimitSelection', indent=1) +## dbg('TimeCtrl::LimitSelection', indent=1) pos = self.GetInsertionPoint() self.__posCurrent = pos sel_start, sel_to = self.GetSelection() @@ -1155,18 +1157,18 @@ class TimeCtrl(BaseMaskedTextCtrl): if sel_to < pos: sel_to = start 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.SetSelection(self.__posCurrent, sel_to) if event: event.Skip() - dbg(indent=0) +## dbg(indent=0) def __IncrementValue(self, key, pos): - dbg('TimeCtrl::IncrementValue', key, pos, indent=1) +## dbg('TimeCtrl::IncrementValue', key, pos, indent=1) text = self.GetValue() field = self._FindField(pos) - dbg('field: ', field._index) +## dbg('field: ', field._index) start, end = field._extent slice = text[start:end] 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: # (Use a fixed date not subject to DST variations:) converter = wx.DateTimeFromDMY(1, 0, 1970) - dbg('text: "%s"' % text) +## dbg('text: "%s"' % text) converter.ParseTime(text.strip()) currenthour = converter.GetHour() - dbg('current hour:', currenthour) +## dbg('current hour:', currenthour) newhour = (currenthour + increment) % 24 - dbg('newhour:', newhour) +## dbg('newhour:', 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() else: # minute or second field; handled the same way: @@ -1202,7 +1204,7 @@ class TimeCtrl(BaseMaskedTextCtrl): except ValueError: # must not be in bounds: if not wx.Validator_IsSilent(): wx.Bell() - dbg(indent=0) +## dbg(indent=0) def _toGUI( self, wxdt ): @@ -1227,23 +1229,23 @@ class TimeCtrl(BaseMaskedTextCtrl): not a valid value for the control as currently specified. 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: - dbg(indent=0) +## dbg(indent=0) raise ValueError('%s not a valid time value' % repr(value)) valid = True # assume true try: value = self.GetWxDateTime(value) # regularize form; can generate ValueError if problem doing so except: - dbg('exception occurred', indent=0) +## dbg('exception occurred', indent=0) raise if self.IsLimited() and not self.IsInBounds(value): - dbg(indent=0) +## dbg(indent=0) raise ValueError ( 'value %s is not within the bounds of the control' % str(value) ) - dbg(indent=0) +## dbg(indent=0) return value #---------------------------------------------------------------------------- @@ -1278,12 +1280,12 @@ if __name__ == '__main__': self.Bind(EVT_TIMEUPDATE, self.OnTimeChange, self.tc) def OnTimeChange(self, event): - dbg('OnTimeChange: value = ', event.GetValue()) +## dbg('OnTimeChange: value = ', event.GetValue()) wxdt = self.tc.GetWxDateTime() - dbg('wxdt =', wxdt.GetHour(), wxdt.GetMinute(), wxdt.GetSecond()) +## dbg('wxdt =', wxdt.GetHour(), wxdt.GetMinute(), wxdt.GetSecond()) if self.test_mx: mxdt = self.tc.GetMxDateTime() - dbg('mxdt =', mxdt.hour, mxdt.minute, mxdt.second) +## dbg('mxdt =', mxdt.hour, mxdt.minute, mxdt.second) class MyApp(wx.App): diff --git a/wxPython/wxPython/lib/maskedctrl.py b/wxPython/wxPython/lib/maskedctrl.py index c022bf40e3..197cd9db65 100644 --- a/wxPython/wxPython/lib/maskedctrl.py +++ b/wxPython/wxPython/lib/maskedctrl.py @@ -2,9 +2,21 @@ ## 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. -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 -wxMaskedCtrl = wx.lib.maskedctrl.MaskedCtrl +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 + +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 diff --git a/wxPython/wxPython/lib/maskededit.py b/wxPython/wxPython/lib/maskededit.py index 42e4331cd5..7c4da9d1c1 100644 --- a/wxPython/wxPython/lib/maskededit.py +++ b/wxPython/wxPython/lib/maskededit.py @@ -1,16 +1,15 @@ ## 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 ## 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 -test = wx.lib.maskededit.test -test2 = wx.lib.maskededit.test2 -wxIpAddrCtrl = wx.lib.maskededit.IpAddrCtrl -wxMaskedComboBox = wx.lib.maskededit.MaskedComboBox -wxMaskedComboBoxSelectEvent = wx.lib.maskededit.MaskedComboBoxSelectEvent -wxMaskedEditMixin = wx.lib.maskededit.MaskedEditMixin -wxMaskedTextCtrl = wx.lib.maskededit.MaskedTextCtrl +wxMaskedEditMixin = wx.lib.masked.MaskedEditMixin +wxMaskedTextCtrl = wx.lib.masked.TextCtrl +wxMaskedComboBox = wx.lib.masked.ComboBox +wxMaskedComboBoxSelectEvent = wx.lib.masked.MaskedComboBoxSelectEvent +wxIpAddrCtrl = wx.lib.masked.IpAddrCtrl diff --git a/wxPython/wxPython/lib/maskednumctrl.py b/wxPython/wxPython/lib/maskednumctrl.py index 99e09bc188..69c9032e62 100644 --- a/wxPython/wxPython/lib/maskednumctrl.py +++ b/wxPython/wxPython/lib/maskednumctrl.py @@ -2,10 +2,10 @@ ## 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. -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 -wxMaskedNumCtrl = wx.lib.maskednumctrl.MaskedNumCtrl -wxMaskedNumNumberUpdatedEvent = wx.lib.maskednumctrl.MaskedNumNumberUpdatedEvent +EVT_MASKEDNUM = wx.lib.masked.numctrl.EVT_NUM +wxMaskedNumCtrl = wx.lib.masked.numctrl.NumCtrl +wxMaskedNumNumberUpdatedEvent = wx.lib.masked.numctrl.NumberUpdatedEvent diff --git a/wxPython/wxPython/lib/timectrl.py b/wxPython/wxPython/lib/timectrl.py index da46bcf97a..d71c4cebd6 100644 --- a/wxPython/wxPython/lib/timectrl.py +++ b/wxPython/wxPython/lib/timectrl.py @@ -2,10 +2,10 @@ ## 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. -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 -TimeUpdatedEvent = wx.lib.timectrl.TimeUpdatedEvent -wxTimeCtrl = wx.lib.timectrl.TimeCtrl +EVT_TIMEUPDATE = wx.lib.masked.timectrl.EVT_TIMEUPDATE +TimeUpdatedEvent = wx.lib.masked.timectrl.TimeUpdatedEvent +wxTimeCtrl = wx.lib.masked.timectrl.TimeCtrl -- 2.47.2