]> git.saurik.com Git - wxWidgets.git/blob - wxPython/wx/lib/masked/textctrl.py
Added new MaskedEditControl code from Will Sadkin. The modules are
[wxWidgets.git] / wxPython / wx / lib / masked / textctrl.py
1 #----------------------------------------------------------------------------
2 # Name: masked.textctrl.py
3 # Authors: Jeff Childers, Will Sadkin
4 # Email: jchilders_98@yahoo.com, wsadkin@nameconnector.com
5 # Created: 02/11/2003
6 # Copyright: (c) 2003 by Jeff Childers, Will Sadkin, 2003
7 # Portions: (c) 2002 by Will Sadkin, 2002-2003
8 # RCS-ID: $Id$
9 # License: wxWidgets license
10 #----------------------------------------------------------------------------
11 #
12 # This file contains the most typically used generic masked control,
13 # masked.TextCtrl. It also defines the BaseMaskedTextCtrl, which can
14 # be used to derive other "semantics-specific" classes, like masked.NumCtrl,
15 # masked.TimeCtrl, and masked.IpAddrCtrl.
16 #
17 #----------------------------------------------------------------------------
18 import wx
19 from wx.lib.masked import *
20
21 # jmg 12/9/03 - when we cut ties with Py 2.2 and earlier, this would
22 # be a good place to implement the 2.3 logger class
23 from wx.tools.dbg import Logger
24 dbg = Logger()
25 ##dbg(enable=0)
26
27 # ## TRICKY BIT: to avoid a ton of boiler-plate, and to
28 # ## automate the getter/setter generation for each valid
29 # ## control parameter so we never forget to add the
30 # ## functions when adding parameters, this loop
31 # ## programmatically adds them to the class:
32 # ## (This makes it easier for Designers like Boa to
33 # ## deal with masked controls.)
34 #
35 # ## To further complicate matters, this is done with an
36 # ## extra level of inheritance, so that "general" classes like
37 # ## MaskedTextCtrl can have all possible attributes,
38 # ## while derived classes, like TimeCtrl and MaskedNumCtrl
39 # ## can prevent exposure of those optional attributes of their base
40 # ## class that do not make sense for their derivation. Therefore,
41 # ## we define
42 # ## BaseMaskedTextCtrl(TextCtrl, MaskedEditMixin)
43 # ## and
44 # ## MaskedTextCtrl(BaseMaskedTextCtrl, MaskedEditAccessorsMixin).
45 # ##
46 # ## This allows us to then derive:
47 # ## MaskedNumCtrl( BaseMaskedTextCtrl )
48 # ##
49 # ## and not have to expose all the same accessor functions for the
50 # ## derived control when they don't all make sense for it.
51 # ##
52
53 class BaseMaskedTextCtrl( wx.TextCtrl, MaskedEditMixin ):
54 """
55 This is the primary derivation from MaskedEditMixin. It provides
56 a general masked text control that can be configured with different
57 masks. It's actually a "base masked textCtrl", so that the
58 MaskedTextCtrl class can be derived from it, and add those
59 accessor functions to it that are appropriate to the general class,
60 whilst other classes can derive from BaseMaskedTextCtrl, and
61 only define those accessor functions that are appropriate for
62 those derivations.
63 """
64
65 def __init__( self, parent, id=-1, value = '',
66 pos = wx.DefaultPosition,
67 size = wx.DefaultSize,
68 style = wx.TE_PROCESS_TAB,
69 validator=wx.DefaultValidator, ## placeholder provided for data-transfer logic
70 name = 'maskedTextCtrl',
71 setupEventHandling = True, ## setup event handling by default
72 **kwargs):
73
74 wx.TextCtrl.__init__(self, parent, id, value='',
75 pos=pos, size = size,
76 style=style, validator=validator,
77 name=name)
78
79 self.controlInitialized = True
80 MaskedEditMixin.__init__( self, name, **kwargs )
81
82 self._SetInitialValue(value)
83
84 if setupEventHandling:
85 ## Setup event handlers
86 self.Bind(wx.EVT_SET_FOCUS, self._OnFocus ) ## defeat automatic full selection
87 self.Bind(wx.EVT_KILL_FOCUS, self._OnKillFocus ) ## run internal validator
88 self.Bind(wx.EVT_LEFT_DCLICK, self._OnDoubleClick) ## select field under cursor on dclick
89 self.Bind(wx.EVT_RIGHT_UP, self._OnContextMenu ) ## bring up an appropriate context menu
90 self.Bind(wx.EVT_KEY_DOWN, self._OnKeyDown ) ## capture control events not normally seen, eg ctrl-tab.
91 self.Bind(wx.EVT_CHAR, self._OnChar ) ## handle each keypress
92 self.Bind(wx.EVT_TEXT, self._OnTextChange ) ## color control appropriately & keep
93 ## track of previous value for undo
94
95
96 def __repr__(self):
97 return "<BaseMaskedTextCtrl: %s>" % self.GetValue()
98
99
100 def _GetSelection(self):
101 """
102 Allow mixin to get the text selection of this control.
103 REQUIRED by any class derived from MaskedEditMixin.
104 """
105 return self.GetSelection()
106
107 def _SetSelection(self, sel_start, sel_to):
108 """
109 Allow mixin to set the text selection of this control.
110 REQUIRED by any class derived from MaskedEditMixin.
111 """
112 #### dbg("MaskedTextCtrl::_SetSelection(%(sel_start)d, %(sel_to)d)" % locals())
113 return self.SetSelection( sel_start, sel_to )
114
115 def SetSelection(self, sel_start, sel_to):
116 """
117 This is just for debugging...
118 """
119 ## dbg("MaskedTextCtrl::SetSelection(%(sel_start)d, %(sel_to)d)" % locals())
120 wx.TextCtrl.SetSelection(self, sel_start, sel_to)
121
122
123 def _GetInsertionPoint(self):
124 return self.GetInsertionPoint()
125
126 def _SetInsertionPoint(self, pos):
127 #### dbg("MaskedTextCtrl::_SetInsertionPoint(%(pos)d)" % locals())
128 self.SetInsertionPoint(pos)
129
130 def SetInsertionPoint(self, pos):
131 """
132 This is just for debugging...
133 """
134 ## dbg("MaskedTextCtrl::SetInsertionPoint(%(pos)d)" % locals())
135 wx.TextCtrl.SetInsertionPoint(self, pos)
136
137
138 def _GetValue(self):
139 """
140 Allow mixin to get the raw value of the control with this function.
141 REQUIRED by any class derived from MaskedEditMixin.
142 """
143 return self.GetValue()
144
145 def _SetValue(self, value):
146 """
147 Allow mixin to set the raw value of the control with this function.
148 REQUIRED by any class derived from MaskedEditMixin.
149 """
150 ## dbg('MaskedTextCtrl::_SetValue("%(value)s")' % locals(), indent=1)
151 # Record current selection and insertion point, for undo
152 self._prevSelection = self._GetSelection()
153 self._prevInsertionPoint = self._GetInsertionPoint()
154 wx.TextCtrl.SetValue(self, value)
155 ## dbg(indent=0)
156
157 def SetValue(self, value):
158 """
159 This function redefines the externally accessible .SetValue to be
160 a smart "paste" of the text in question, so as not to corrupt the
161 masked control. NOTE: this must be done in the class derived
162 from the base wx control.
163 """
164 ## dbg('MaskedTextCtrl::SetValue = "%s"' % value, indent=1)
165
166 if not self._mask:
167 wx.TextCtrl.SetValue(self, value) # revert to base control behavior
168 return
169
170 # empty previous contents, replacing entire value:
171 self._SetInsertionPoint(0)
172 self._SetSelection(0, self._masklength)
173 if self._signOk and self._useParens:
174 signpos = value.find('-')
175 if signpos != -1:
176 value = value[:signpos] + '(' + value[signpos+1:].strip() + ')'
177 elif value.find(')') == -1 and len(value) < self._masklength:
178 value += ' ' # add place holder for reserved space for right paren
179
180 if( len(value) < self._masklength # value shorter than control
181 and (self._isFloat or self._isInt) # and it's a numeric control
182 and self._ctrl_constraints._alignRight ): # and it's a right-aligned control
183
184 ## dbg('len(value)', len(value), ' < self._masklength', self._masklength)
185 # try to intelligently "pad out" the value to the right size:
186 value = self._template[0:self._masklength - len(value)] + value
187 if self._isFloat and value.find('.') == -1:
188 value = value[1:]
189 ## dbg('padded value = "%s"' % value)
190
191 # make SetValue behave the same as if you had typed the value in:
192 try:
193 value = self._Paste(value, raise_on_invalid=True, just_return_value=True)
194 if self._isFloat:
195 self._isNeg = False # (clear current assumptions)
196 value = self._adjustFloat(value)
197 elif self._isInt:
198 self._isNeg = False # (clear current assumptions)
199 value = self._adjustInt(value)
200 elif self._isDate and not self.IsValid(value) and self._4digityear:
201 value = self._adjustDate(value, fixcentury=True)
202 except ValueError:
203 # If date, year might be 2 digits vs. 4; try adjusting it:
204 if self._isDate and self._4digityear:
205 dateparts = value.split(' ')
206 dateparts[0] = self._adjustDate(dateparts[0], fixcentury=True)
207 value = string.join(dateparts, ' ')
208 ## dbg('adjusted value: "%s"' % value)
209 value = self._Paste(value, raise_on_invalid=True, just_return_value=True)
210 else:
211 ## dbg('exception thrown', indent=0)
212 raise
213
214 self._SetValue(value) # note: to preserve similar capability, .SetValue()
215 # does not change IsModified()
216 #### dbg('queuing insertion after .SetValue', self._masklength)
217 wx.CallAfter(self._SetInsertionPoint, self._masklength)
218 wx.CallAfter(self._SetSelection, self._masklength, self._masklength)
219 ## dbg(indent=0)
220
221
222 def Clear(self):
223 """ Blanks the current control value by replacing it with the default value."""
224 ## dbg("MaskedTextCtrl::Clear - value reset to default value (template)")
225 if self._mask:
226 self.ClearValue()
227 else:
228 wx.TextCtrl.Clear(self) # else revert to base control behavior
229
230
231 def _Refresh(self):
232 """
233 Allow mixin to refresh the base control with this function.
234 REQUIRED by any class derived from MaskedEditMixin.
235 """
236 ## dbg('MaskedTextCtrl::_Refresh', indent=1)
237 wx.TextCtrl.Refresh(self)
238 ## dbg(indent=0)
239
240
241 def Refresh(self):
242 """
243 This function redefines the externally accessible .Refresh() to
244 validate the contents of the masked control as it refreshes.
245 NOTE: this must be done in the class derived from the base wx control.
246 """
247 ## dbg('MaskedTextCtrl::Refresh', indent=1)
248 self._CheckValid()
249 self._Refresh()
250 ## dbg(indent=0)
251
252
253 def _IsEditable(self):
254 """
255 Allow mixin to determine if the base control is editable with this function.
256 REQUIRED by any class derived from MaskedEditMixin.
257 """
258 return wx.TextCtrl.IsEditable(self)
259
260
261 def Cut(self):
262 """
263 This function redefines the externally accessible .Cut to be
264 a smart "erase" of the text in question, so as not to corrupt the
265 masked control. NOTE: this must be done in the class derived
266 from the base wx control.
267 """
268 if self._mask:
269 self._Cut() # call the mixin's Cut method
270 else:
271 wx.TextCtrl.Cut(self) # else revert to base control behavior
272
273
274 def Paste(self):
275 """
276 This function redefines the externally accessible .Paste to be
277 a smart "paste" of the text in question, so as not to corrupt the
278 masked control. NOTE: this must be done in the class derived
279 from the base wx control.
280 """
281 if self._mask:
282 self._Paste() # call the mixin's Paste method
283 else:
284 wx.TextCtrl.Paste(self, value) # else revert to base control behavior
285
286
287 def Undo(self):
288 """
289 This function defines the undo operation for the control. (The default
290 undo is 1-deep.)
291 """
292 if self._mask:
293 self._Undo()
294 else:
295 wx.TextCtrl.Undo(self) # else revert to base control behavior
296
297
298 def IsModified(self):
299 """
300 This function overrides the raw wxTextCtrl method, because the
301 masked edit mixin uses SetValue to change the value, which doesn't
302 modify the state of this attribute. So, we keep track on each
303 keystroke to see if the value changes, and if so, it's been
304 modified.
305 """
306 return wx.TextCtrl.IsModified(self) or self.modified
307
308
309 def _CalcSize(self, size=None):
310 """
311 Calculate automatic size if allowed; use base mixin function.
312 """
313 return self._calcSize(size)
314
315
316 class TextCtrl( BaseMaskedTextCtrl, MaskedEditAccessorsMixin ):
317 """
318 This extra level of inheritance allows us to add the generic set of
319 masked edit parameters only to this class while allowing other
320 classes to derive from the "base" masked text control, and provide
321 a smaller set of valid accessor functions.
322 """
323 pass
324
325