]> git.saurik.com Git - wxWidgets.git/blob - wxPython/wx/lib/masked/textctrl.py
Patch from Will Sadkin:
[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, replace_to = 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, replace_to = 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', replace_to)
217 # set selection to last char replaced by paste
218 wx.CallAfter(self._SetInsertionPoint, replace_to)
219 wx.CallAfter(self._SetSelection, replace_to, replace_to)
220 ## dbg(indent=0)
221
222
223 def Clear(self):
224 """ Blanks the current control value by replacing it with the default value."""
225 ## dbg("MaskedTextCtrl::Clear - value reset to default value (template)")
226 if self._mask:
227 self.ClearValue()
228 else:
229 wx.TextCtrl.Clear(self) # else revert to base control behavior
230
231
232 def _Refresh(self):
233 """
234 Allow mixin to refresh the base control with this function.
235 REQUIRED by any class derived from MaskedEditMixin.
236 """
237 ## dbg('MaskedTextCtrl::_Refresh', indent=1)
238 wx.TextCtrl.Refresh(self)
239 ## dbg(indent=0)
240
241
242 def Refresh(self):
243 """
244 This function redefines the externally accessible .Refresh() to
245 validate the contents of the masked control as it refreshes.
246 NOTE: this must be done in the class derived from the base wx control.
247 """
248 ## dbg('MaskedTextCtrl::Refresh', indent=1)
249 self._CheckValid()
250 self._Refresh()
251 ## dbg(indent=0)
252
253
254 def _IsEditable(self):
255 """
256 Allow mixin to determine if the base control is editable with this function.
257 REQUIRED by any class derived from MaskedEditMixin.
258 """
259 return wx.TextCtrl.IsEditable(self)
260
261
262 def Cut(self):
263 """
264 This function redefines the externally accessible .Cut to be
265 a smart "erase" of the text in question, so as not to corrupt the
266 masked control. NOTE: this must be done in the class derived
267 from the base wx control.
268 """
269 if self._mask:
270 self._Cut() # call the mixin's Cut method
271 else:
272 wx.TextCtrl.Cut(self) # else revert to base control behavior
273
274
275 def Paste(self):
276 """
277 This function redefines the externally accessible .Paste to be
278 a smart "paste" of the text in question, so as not to corrupt the
279 masked control. NOTE: this must be done in the class derived
280 from the base wx control.
281 """
282 if self._mask:
283 self._Paste() # call the mixin's Paste method
284 else:
285 wx.TextCtrl.Paste(self, value) # else revert to base control behavior
286
287
288 def Undo(self):
289 """
290 This function defines the undo operation for the control. (The default
291 undo is 1-deep.)
292 """
293 if self._mask:
294 self._Undo()
295 else:
296 wx.TextCtrl.Undo(self) # else revert to base control behavior
297
298
299 def IsModified(self):
300 """
301 This function overrides the raw wxTextCtrl method, because the
302 masked edit mixin uses SetValue to change the value, which doesn't
303 modify the state of this attribute. So, we keep track on each
304 keystroke to see if the value changes, and if so, it's been
305 modified.
306 """
307 return wx.TextCtrl.IsModified(self) or self.modified
308
309
310 def _CalcSize(self, size=None):
311 """
312 Calculate automatic size if allowed; use base mixin function.
313 """
314 return self._calcSize(size)
315
316
317 class TextCtrl( BaseMaskedTextCtrl, MaskedEditAccessorsMixin ):
318 """
319 This extra level of inheritance allows us to add the generic set of
320 masked edit parameters only to this class while allowing other
321 classes to derive from the "base" masked text control, and provide
322 a smaller set of valid accessor functions.
323 """
324 pass
325
326