]> git.saurik.com Git - wxWidgets.git/blob - wxPython/wx/lib/masked/timectrl.py
check for wxHAS_HUGE_FILES
[wxWidgets.git] / wxPython / wx / lib / masked / timectrl.py
1 #----------------------------------------------------------------------------
2 # Name: timectrl.py
3 # Author: Will Sadkin
4 # Created: 09/19/2002
5 # Copyright: (c) 2002 by Will Sadkin, 2002
6 # RCS-ID: $Id$
7 # License: wxWindows license
8 #----------------------------------------------------------------------------
9 # NOTE:
10 # This was written way it is because of the lack of masked edit controls
11 # in wxWindows/wxPython. I would also have preferred to derive this
12 # control from a wxSpinCtrl rather than wxTextCtrl, but the wxTextCtrl
13 # component of that control is inaccessible through the interface exposed in
14 # wxPython.
15 #
16 # TimeCtrl does not use validators, because it does careful manipulation
17 # of the cursor in the text window on each keystroke, and validation is
18 # cursor-position specific, so the control intercepts the key codes before the
19 # validator would fire.
20 #
21 # TimeCtrl now also supports .SetValue() with either strings or wxDateTime
22 # values, as well as range limits, with the option of either enforcing them
23 # or simply coloring the text of the control if the limits are exceeded.
24 #
25 # Note: this class now makes heavy use of wxDateTime for parsing and
26 # regularization, but it always does so with ephemeral instances of
27 # wxDateTime, as the C++/Python validity of these instances seems to not
28 # persist. Because "today" can be a day for which an hour can "not exist"
29 # or be counted twice (1 day each per year, for DST adjustments), the date
30 # portion of all wxDateTimes used/returned have their date portion set to
31 # Jan 1, 1970 (the "epoch.")
32 #----------------------------------------------------------------------------
33 # 12/13/2003 - Jeff Grimmett (grimmtooth@softhome.net)
34 #
35 # o Updated for V2.5 compatability
36 # o wx.SpinCtl has some issues that cause the control to
37 # lock up. Noted in other places using it too, it's not this module
38 # that's at fault.
39 #
40 # 12/20/2003 - Jeff Grimmett (grimmtooth@softhome.net)
41 #
42 # o wxMaskedTextCtrl -> masked.TextCtrl
43 # o wxTimeCtrl -> masked.TimeCtrl
44 #
45
46 """<html><body>
47 <P>
48 <B>TimeCtrl</B> provides a multi-cell control that allows manipulation of a time
49 value. It supports 12 or 24 hour format, and you can use wxDateTime or mxDateTime
50 to get/set values from the control.
51 <P>
52 Left/right/tab keys to switch cells within a TimeCtrl, and the up/down arrows act
53 like a spin control. TimeCtrl also allows for an actual spin button to be attached
54 to the control, so that it acts like the up/down arrow keys.
55 <P>
56 The <B>!</B> or <B>c</B> key sets the value of the control to the current time.
57 <P>
58 Here's the API for TimeCtrl:
59 <DL><PRE>
60 <B>TimeCtrl</B>(
61 parent, id = -1,
62 <B>value</B> = '00:00:00',
63 pos = wx.DefaultPosition,
64 size = wx.DefaultSize,
65 <B>style</B> = wxTE_PROCESS_TAB,
66 <B>validator</B> = wx.DefaultValidator,
67 name = "time",
68 <B>format</B> = 'HHMMSS',
69 <B>fmt24hr</B> = False,
70 <B>displaySeconds</B> = True,
71 <B>spinButton</B> = None,
72 <B>min</B> = None,
73 <B>max</B> = None,
74 <B>limited</B> = None,
75 <B>oob_color</B> = "Yellow"
76 )
77 </PRE>
78 <UL>
79 <DT><B>value</B>
80 <DD>If no initial value is set, the default will be midnight; if an illegal string
81 is specified, a ValueError will result. (You can always later set the initial time
82 with SetValue() after instantiation of the control.)
83 <DL><B>size</B>
84 <DD>The size of the control will be automatically adjusted for 12/24 hour format
85 if wx.DefaultSize is specified. NOTE: due to a problem with wx.DateTime, if the
86 locale does not use 'AM/PM' for its values, the default format will automatically
87 change to 24 hour format, and an AttributeError will be thrown if a non-24 format
88 is specified.
89 <DT><B>style</B>
90 <DD>By default, TimeCtrl will process TAB events, by allowing tab to the
91 different cells within the control.
92 <DT><B>validator</B>
93 <DD>By default, TimeCtrl just uses the default (empty) validator, as all
94 of its validation for entry control is handled internally. However, a validator
95 can be supplied to provide data transfer capability to the control.
96 <BR>
97 <DT><B>format</B>
98 <DD>This parameter can be used instead of the fmt24hr and displaySeconds
99 parameters, respectively; it provides a shorthand way to specify the time
100 format you want. Accepted values are 'HHMMSS', 'HHMM', '24HHMMSS', and
101 '24HHMM'. If the format is specified, the other two arguments will be ignored.
102 <BR>
103 <DT><B>fmt24hr</B>
104 <DD>If True, control will display time in 24 hour time format; if False, it will
105 use 12 hour AM/PM format. SetValue() will adjust values accordingly for the
106 control, based on the format specified. (This value is ignored if the <i>format</i>
107 parameter is specified.)
108 <BR>
109 <DT><B>displaySeconds</B>
110 <DD>If True, control will include a seconds field; if False, it will
111 just show hours and minutes. (This value is ignored if the <i>format</i>
112 parameter is specified.)
113 <BR>
114 <DT><B>spinButton</B>
115 <DD>If specified, this button's events will be bound to the behavior of the
116 TimeCtrl, working like up/down cursor key events. (See BindSpinButton.)
117 <BR>
118 <DT><B>min</B>
119 <DD>Defines the lower bound for "valid" selections in the control.
120 By default, TimeCtrl doesn't have bounds. You must set both upper and lower
121 bounds to make the control pay attention to them, (as only one bound makes no sense
122 with times.) "Valid" times will fall between the min and max "pie wedge" of the
123 clock.
124 <DT><B>max</B>
125 <DD>Defines the upper bound for "valid" selections in the control.
126 "Valid" times will fall between the min and max "pie wedge" of the
127 clock. (This can be a "big piece", ie. <b>min = 11pm, max= 10pm</b>
128 means <I>all but the hour from 10:00pm to 11pm are valid times.</I>)
129 <DT><B>limited</B>
130 <DD>If True, the control will not permit entry of values that fall outside the
131 set bounds.
132 <BR>
133 <DT><B>oob_color</B>
134 <DD>Sets the background color used to indicate out-of-bounds values for the control
135 when the control is not limited. This is set to "Yellow" by default.
136 </DL>
137 </UL>
138 <BR>
139 <BR>
140 <BR>
141 <DT><B>EVT_TIMEUPDATE(win, id, func)</B>
142 <DD>func is fired whenever the value of the control changes.
143 <BR>
144 <BR>
145 <DT><B>SetValue(time_string | wxDateTime | wxTimeSpan | mx.DateTime | mx.DateTimeDelta)</B>
146 <DD>Sets the value of the control to a particular time, given a valid
147 value; raises ValueError on invalid value.
148 <EM>NOTE:</EM> This will only allow mx.DateTime or mx.DateTimeDelta if mx.DateTime
149 was successfully imported by the class module.
150 <BR>
151 <DT><B>GetValue(as_wxDateTime = False, as_mxDateTime = False, as_wxTimeSpan=False, as mxDateTimeDelta=False)</B>
152 <DD>Retrieves the value of the time from the control. By default this is
153 returned as a string, unless one of the other arguments is set; args are
154 searched in the order listed; only one value will be returned.
155 <BR>
156 <DT><B>GetWxDateTime(value=None)</B>
157 <DD>When called without arguments, retrieves the value of the control, and applies
158 it to the wxDateTimeFromHMS() constructor, and returns the resulting value.
159 The date portion will always be set to Jan 1, 1970. This form is the same
160 as GetValue(as_wxDateTime=True). GetWxDateTime can also be called with any of the
161 other valid time formats settable with SetValue, to regularize it to a single
162 wxDateTime form. The function will raise ValueError on an unconvertable argument.
163 <BR>
164 <DT><B>GetMxDateTime()</B>
165 <DD>Retrieves the value of the control and applies it to the DateTime.Time()
166 constructor,and returns the resulting value. (The date portion will always be
167 set to Jan 1, 1970.) (Same as GetValue(as_wxDateTime=True); provided for backward
168 compatibility with previous release.)
169 <BR>
170 <BR>
171 <DT><B>BindSpinButton(SpinBtton)</B>
172 <DD>Binds an externally created spin button to the control, so that up/down spin
173 events change the active cell or selection in the control (in addition to the
174 up/down cursor keys.) (This is primarily to allow you to create a "standard"
175 interface to time controls, as seen in Windows.)
176 <BR>
177 <BR>
178 <DT><B>SetMin(min=None)</B>
179 <DD>Sets the expected minimum value, or lower bound, of the control.
180 (The lower bound will only be enforced if the control is
181 configured to limit its values to the set bounds.)
182 If a value of <I>None</I> is provided, then the control will have
183 explicit lower bound. If the value specified is greater than
184 the current lower bound, then the function returns False and the
185 lower bound will not change from its current setting. On success,
186 the function returns True. Even if set, if there is no corresponding
187 upper bound, the control will behave as if it is unbounded.
188 <DT><DD>If successful and the current value is outside the
189 new bounds, if the control is limited the value will be
190 automatically adjusted to the nearest bound; if not limited,
191 the background of the control will be colored with the current
192 out-of-bounds color.
193 <BR>
194 <DT><B>GetMin(as_string=False)</B>
195 <DD>Gets the current lower bound value for the control, returning
196 None, if not set, or a wxDateTime, unless the as_string parameter
197 is set to True, at which point it will return the string
198 representation of the lower bound.
199 <BR>
200 <BR>
201 <DT><B>SetMax(max=None)</B>
202 <DD>Sets the expected maximum value, or upper bound, of the control.
203 (The upper bound will only be enforced if the control is
204 configured to limit its values to the set bounds.)
205 If a value of <I>None</I> is provided, then the control will
206 have no explicit upper bound. If the value specified is less
207 than the current lower bound, then the function returns False and
208 the maximum will not change from its current setting. On success,
209 the function returns True. Even if set, if there is no corresponding
210 lower bound, the control will behave as if it is unbounded.
211 <DT><DD>If successful and the current value is outside the
212 new bounds, if the control is limited the value will be
213 automatically adjusted to the nearest bound; if not limited,
214 the background of the control will be colored with the current
215 out-of-bounds color.
216 <BR>
217 <DT><B>GetMax(as_string = False)</B>
218 <DD>Gets the current upper bound value for the control, returning
219 None, if not set, or a wxDateTime, unless the as_string parameter
220 is set to True, at which point it will return the string
221 representation of the lower bound.
222
223 <BR>
224 <BR>
225 <DT><B>SetBounds(min=None,max=None)</B>
226 <DD>This function is a convenience function for setting the min and max
227 values at the same time. The function only applies the maximum bound
228 if setting the minimum bound is successful, and returns True
229 only if both operations succeed. <B><I>Note: leaving out an argument
230 will remove the corresponding bound, and result in the behavior of
231 an unbounded control.</I></B>
232 <BR>
233 <DT><B>GetBounds(as_string = False)</B>
234 <DD>This function returns a two-tuple (min,max), indicating the
235 current bounds of the control. Each value can be None if
236 that bound is not set. The values will otherwise be wxDateTimes
237 unless the as_string argument is set to True, at which point they
238 will be returned as string representations of the bounds.
239 <BR>
240 <BR>
241 <DT><B>IsInBounds(value=None)</B>
242 <DD>Returns <I>True</I> if no value is specified and the current value
243 of the control falls within the current bounds. This function can also
244 be called with a value to see if that value would fall within the current
245 bounds of the given control. It will raise ValueError if the value
246 specified is not a wxDateTime, mxDateTime (if available) or parsable string.
247 <BR>
248 <BR>
249 <DT><B>IsValid(value)</B>
250 <DD>Returns <I>True</I>if specified value is a legal time value and
251 falls within the current bounds of the given control.
252 <BR>
253 <BR>
254 <DT><B>SetLimited(bool)</B>
255 <DD>If called with a value of True, this function will cause the control
256 to limit the value to fall within the bounds currently specified.
257 (Provided both bounds have been set.)
258 If the control's value currently exceeds the bounds, it will then
259 be set to the nearest bound.
260 If called with a value of False, this function will disable value
261 limiting, but coloring of out-of-bounds values will still take
262 place if bounds have been set for the control.
263 <DT><B>IsLimited()</B>
264 <DD>Returns <I>True</I> if the control is currently limiting the
265 value to fall within the current bounds.
266 <BR>
267 </DL>
268 </body></html>
269 """
270
271 import copy
272 import string
273 import types
274
275 import wx
276
277 from wx.tools.dbg import Logger
278 from wx.lib.masked import Field, BaseMaskedTextCtrl
279
280 dbg = Logger()
281 ##dbg(enable=0)
282
283 try:
284 from mx import DateTime
285 accept_mx = True
286 except ImportError:
287 accept_mx = False
288
289 # This class of event fires whenever the value of the time changes in the control:
290 wxEVT_TIMEVAL_UPDATED = wx.NewEventType()
291 EVT_TIMEUPDATE = wx.PyEventBinder(wxEVT_TIMEVAL_UPDATED, 1)
292
293 class TimeUpdatedEvent(wx.PyCommandEvent):
294 def __init__(self, id, value ='12:00:00 AM'):
295 wx.PyCommandEvent.__init__(self, wxEVT_TIMEVAL_UPDATED, id)
296 self.value = value
297 def GetValue(self):
298 """Retrieve the value of the time control at the time this event was generated"""
299 return self.value
300
301 class TimeCtrlAccessorsMixin:
302 # Define TimeCtrl's list of attributes having their own
303 # Get/Set functions, ignoring those that make no sense for
304 # an numeric control.
305 exposed_basectrl_params = (
306 'defaultValue',
307 'description',
308
309 'useFixedWidthFont',
310 'emptyBackgroundColour',
311 'validBackgroundColour',
312 'invalidBackgroundColour',
313
314 'validFunc',
315 'validRequired',
316 )
317 for param in exposed_basectrl_params:
318 propname = param[0].upper() + param[1:]
319 exec('def Set%s(self, value): self.SetCtrlParameters(%s=value)' % (propname, param))
320 exec('def Get%s(self): return self.GetCtrlParameter("%s")''' % (propname, param))
321
322 if param.find('Colour') != -1:
323 # add non-british spellings, for backward-compatibility
324 propname.replace('Colour', 'Color')
325
326 exec('def Set%s(self, value): self.SetCtrlParameters(%s=value)' % (propname, param))
327 exec('def Get%s(self): return self.GetCtrlParameter("%s")''' % (propname, param))
328
329
330 class TimeCtrl(BaseMaskedTextCtrl):
331
332 valid_ctrl_params = {
333 'format' : 'HHMMSS', # default format code
334 'displaySeconds' : True, # by default, shows seconds
335 'min': None, # by default, no bounds set
336 'max': None,
337 'limited': False, # by default, no limiting even if bounds set
338 'useFixedWidthFont': True, # by default, use a fixed-width font
339 'oob_color': "Yellow" # by default, the default masked.TextCtrl "invalid" color
340 }
341
342 def __init__ (
343 self, parent, id=-1, value = '00:00:00',
344 pos = wx.DefaultPosition, size = wx.DefaultSize,
345 fmt24hr=False,
346 spinButton = None,
347 style = wx.TE_PROCESS_TAB,
348 validator = wx.DefaultValidator,
349 name = "time",
350 **kwargs ):
351
352 # set defaults for control:
353 ## dbg('setting defaults:')
354
355 self.__fmt24hr = False
356 wxdt = wx.DateTimeFromDMY(1, 0, 1970)
357 if wxdt.Format('%p') != 'AM':
358 TimeCtrl.valid_ctrl_params['format'] = '24HHMMSS'
359 self.__fmt24hr = True
360 fmt24hr = True # force/change default positional argument
361 # (will countermand explicit set to False too.)
362
363 for key, param_value in TimeCtrl.valid_ctrl_params.items():
364 # This is done this way to make setattr behave consistently with
365 # "private attribute" name mangling
366 setattr(self, "_TimeCtrl__" + key, copy.copy(param_value))
367
368 # create locals from current defaults, so we can override if
369 # specified in kwargs, and handle uniformly:
370 min = self.__min
371 max = self.__max
372 limited = self.__limited
373 self.__posCurrent = 0
374 # handle deprecated keword argument name:
375 if kwargs.has_key('display_seconds'):
376 kwargs['displaySeconds'] = kwargs['display_seconds']
377 del kwargs['display_seconds']
378 if not kwargs.has_key('displaySeconds'):
379 kwargs['displaySeconds'] = True
380
381 # (handle positional arg (from original release) differently from rest of kwargs:)
382 if not kwargs.has_key('format'):
383 if fmt24hr:
384 if kwargs.has_key('displaySeconds') and kwargs['displaySeconds']:
385 kwargs['format'] = '24HHMMSS'
386 del kwargs['displaySeconds']
387 else:
388 kwargs['format'] = '24HHMM'
389 else:
390 if kwargs.has_key('displaySeconds') and kwargs['displaySeconds']:
391 kwargs['format'] = 'HHMMSS'
392 del kwargs['displaySeconds']
393 else:
394 kwargs['format'] = 'HHMM'
395
396 if not kwargs.has_key('useFixedWidthFont'):
397 # allow control over font selection:
398 kwargs['useFixedWidthFont'] = self.__useFixedWidthFont
399
400 maskededit_kwargs = self.SetParameters(**kwargs)
401
402 # allow for explicit size specification:
403 if size != wx.DefaultSize:
404 # override (and remove) "autofit" autoformat code in standard time formats:
405 maskededit_kwargs['formatcodes'] = 'T!'
406
407 # This allows range validation if set
408 maskededit_kwargs['validFunc'] = self.IsInBounds
409
410 # This allows range limits to affect insertion into control or not
411 # dynamically without affecting individual field constraint validation
412 maskededit_kwargs['retainFieldValidation'] = True
413
414 # Now we can initialize the base control:
415 BaseMaskedTextCtrl.__init__(
416 self, parent, id=id,
417 pos=pos, size=size,
418 style = style,
419 validator = validator,
420 name = name,
421 setupEventHandling = False,
422 **maskededit_kwargs)
423
424
425 # This makes ':' act like tab (after we fix each ':' key event to remove "shift")
426 self._SetKeyHandler(':', self._OnChangeField)
427
428
429 # This makes the up/down keys act like spin button controls:
430 self._SetKeycodeHandler(wx.WXK_UP, self.__OnSpinUp)
431 self._SetKeycodeHandler(wx.WXK_DOWN, self.__OnSpinDown)
432
433
434 # This allows ! and c/C to set the control to the current time:
435 self._SetKeyHandler('!', self.__OnSetToNow)
436 self._SetKeyHandler('c', self.__OnSetToNow)
437 self._SetKeyHandler('C', self.__OnSetToNow)
438
439
440 # Set up event handling ourselves, so we can insert special
441 # processing on the ":' key to remove the "shift" attribute
442 # *before* the default handlers have been installed, so
443 # that : takes you forward, not back, and so we can issue
444 # EVT_TIMEUPDATE events on changes:
445
446 self.Bind(wx.EVT_SET_FOCUS, self._OnFocus ) ## defeat automatic full selection
447 self.Bind(wx.EVT_KILL_FOCUS, self._OnKillFocus ) ## run internal validator
448 self.Bind(wx.EVT_LEFT_UP, self.__LimitSelection) ## limit selections to single field
449 self.Bind(wx.EVT_LEFT_DCLICK, self._OnDoubleClick ) ## select field under cursor on dclick
450 self.Bind(wx.EVT_KEY_DOWN, self._OnKeyDown ) ## capture control events not normally seen, eg ctrl-tab.
451 self.Bind(wx.EVT_CHAR, self.__OnChar ) ## remove "shift" attribute from colon key event,
452 ## then call BaseMaskedTextCtrl._OnChar with
453 ## the possibly modified event.
454 self.Bind(wx.EVT_TEXT, self.__OnTextChange, self ) ## color control appropriately and EVT_TIMEUPDATE events
455
456
457 # Validate initial value and set if appropriate
458 try:
459 self.SetBounds(min, max)
460 self.SetLimited(limited)
461 self.SetValue(value)
462 except:
463 self.SetValue('00:00:00')
464
465 if spinButton:
466 self.BindSpinButton(spinButton) # bind spin button up/down events to this control
467
468
469 def SetParameters(self, **kwargs):
470 ## dbg('TimeCtrl::SetParameters(%s)' % repr(kwargs), indent=1)
471 maskededit_kwargs = {}
472 reset_format = False
473
474 if kwargs.has_key('display_seconds'):
475 kwargs['displaySeconds'] = kwargs['display_seconds']
476 del kwargs['display_seconds']
477 if kwargs.has_key('format') and kwargs.has_key('displaySeconds'):
478 del kwargs['displaySeconds'] # always apply format if specified
479
480 # assign keyword args as appropriate:
481 for key, param_value in kwargs.items():
482 if key not in TimeCtrl.valid_ctrl_params.keys():
483 raise AttributeError('invalid keyword argument "%s"' % key)
484
485 if key == 'format':
486 wxdt = wx.DateTimeFromDMY(1, 0, 1970)
487 if wxdt.Format('%p') != 'AM':
488 require24hr = True
489 else:
490 require24hr = False
491
492 # handle both local or generic 'maskededit' autoformat codes:
493 if param_value == 'HHMMSS' or param_value == 'TIMEHHMMSS':
494 self.__displaySeconds = True
495 self.__fmt24hr = False
496 elif param_value == 'HHMM' or param_value == 'TIMEHHMM':
497 self.__displaySeconds = False
498 self.__fmt24hr = False
499 elif param_value == '24HHMMSS' or param_value == '24HRTIMEHHMMSS':
500 self.__displaySeconds = True
501 self.__fmt24hr = True
502 elif param_value == '24HHMM' or param_value == '24HRTIMEHHMM':
503 self.__displaySeconds = False
504 self.__fmt24hr = True
505 else:
506 raise AttributeError('"%s" is not a valid format' % param_value)
507
508 if require24hr and not self.__fmt24hr:
509 raise AttributeError('"%s" is an unsupported time format for the current locale' % param_value)
510
511 reset_format = True
512
513 elif key in ("displaySeconds", "display_seconds") and not kwargs.has_key('format'):
514 self.__displaySeconds = param_value
515 reset_format = True
516
517 elif key == "min": min = param_value
518 elif key == "max": max = param_value
519 elif key == "limited": limited = param_value
520
521 elif key == "useFixedWidthFont":
522 maskededit_kwargs[key] = param_value
523
524 elif key == "oob_color":
525 maskededit_kwargs['invalidBackgroundColor'] = param_value
526
527 if reset_format:
528 if self.__fmt24hr:
529 if self.__displaySeconds: maskededit_kwargs['autoformat'] = '24HRTIMEHHMMSS'
530 else: maskededit_kwargs['autoformat'] = '24HRTIMEHHMM'
531
532 # Set hour field to zero-pad, right-insert, require explicit field change,
533 # select entire field on entry, and require a resultant valid entry
534 # to allow character entry:
535 hourfield = Field(formatcodes='0r<SV', validRegex='0\d|1\d|2[0123]', validRequired=True)
536 else:
537 if self.__displaySeconds: maskededit_kwargs['autoformat'] = 'TIMEHHMMSS'
538 else: maskededit_kwargs['autoformat'] = 'TIMEHHMM'
539
540 # Set hour field to allow spaces (at start), right-insert,
541 # require explicit field change, select entire field on entry,
542 # and require a resultant valid entry to allow character entry:
543 hourfield = Field(formatcodes='_0<rSV', validRegex='0[1-9]| [1-9]|1[012]', validRequired=True)
544 ampmfield = Field(formatcodes='S', emptyInvalid = True, validRequired = True)
545
546 # Field 1 is always a zero-padded right-insert minute field,
547 # similarly configured as above:
548 minutefield = Field(formatcodes='0r<SV', validRegex='[0-5]\d', validRequired=True)
549
550 fields = [ hourfield, minutefield ]
551 if self.__displaySeconds:
552 fields.append(copy.copy(minutefield)) # second field has same constraints as field 1
553
554 if not self.__fmt24hr:
555 fields.append(ampmfield)
556
557 # set fields argument:
558 maskededit_kwargs['fields'] = fields
559
560 # This allows range validation if set
561 maskededit_kwargs['validFunc'] = self.IsInBounds
562
563 # This allows range limits to affect insertion into control or not
564 # dynamically without affecting individual field constraint validation
565 maskededit_kwargs['retainFieldValidation'] = True
566
567 if hasattr(self, 'controlInitialized') and self.controlInitialized:
568 self.SetCtrlParameters(**maskededit_kwargs) # set appropriate parameters
569
570 # Validate initial value and set if appropriate
571 try:
572 self.SetBounds(min, max)
573 self.SetLimited(limited)
574 self.SetValue(value)
575 except:
576 self.SetValue('00:00:00')
577 ## dbg(indent=0)
578 return {} # no arguments to return
579 else:
580 ## dbg(indent=0)
581 return maskededit_kwargs
582
583
584 def BindSpinButton(self, sb):
585 """
586 This function binds an externally created spin button to the control, so that
587 up/down events from the button automatically change the control.
588 """
589 ## dbg('TimeCtrl::BindSpinButton')
590 self.__spinButton = sb
591 if self.__spinButton:
592 # bind event handlers to spin ctrl
593 self.__spinButton.Bind(wx.EVT_SPIN_UP, self.__OnSpinUp, self.__spinButton)
594 self.__spinButton.Bind(wx.EVT_SPIN_DOWN, self.__OnSpinDown, self.__spinButton)
595
596
597 def __repr__(self):
598 return "<TimeCtrl: %s>" % self.GetValue()
599
600
601 def SetValue(self, value):
602 """
603 Validating SetValue function for time values:
604 This function will do dynamic type checking on the value argument,
605 and convert wxDateTime, mxDateTime, or 12/24 format time string
606 into the appropriate format string for the control.
607 """
608 ## dbg('TimeCtrl::SetValue(%s)' % repr(value), indent=1)
609 try:
610 strtime = self._toGUI(self.__validateValue(value))
611 except:
612 ## dbg('validation failed', indent=0)
613 raise
614
615 ## dbg('strtime:', strtime)
616 self._SetValue(strtime)
617 ## dbg(indent=0)
618
619 def GetValue(self,
620 as_wxDateTime = False,
621 as_mxDateTime = False,
622 as_wxTimeSpan = False,
623 as_mxDateTimeDelta = False):
624
625
626 if as_wxDateTime or as_mxDateTime or as_wxTimeSpan or as_mxDateTimeDelta:
627 value = self.GetWxDateTime()
628 if as_wxDateTime:
629 pass
630 elif as_mxDateTime:
631 value = DateTime.DateTime(1970, 1, 1, value.GetHour(), value.GetMinute(), value.GetSecond())
632 elif as_wxTimeSpan:
633 value = wx.TimeSpan(value.GetHour(), value.GetMinute(), value.GetSecond())
634 elif as_mxDateTimeDelta:
635 value = DateTime.DateTimeDelta(0, value.GetHour(), value.GetMinute(), value.GetSecond())
636 else:
637 value = BaseMaskedTextCtrl.GetValue(self)
638 return value
639
640
641 def SetWxDateTime(self, wxdt):
642 """
643 Because SetValue can take a wxDateTime, this is now just an alias.
644 """
645 self.SetValue(wxdt)
646
647
648 def GetWxDateTime(self, value=None):
649 """
650 This function is the conversion engine for TimeCtrl; it takes
651 one of the following types:
652 time string
653 wxDateTime
654 wxTimeSpan
655 mxDateTime
656 mxDateTimeDelta
657 and converts it to a wxDateTime that always has Jan 1, 1970 as its date
658 portion, so that range comparisons around values can work using
659 wxDateTime's built-in comparison function. If a value is not
660 provided to convert, the string value of the control will be used.
661 If the value is not one of the accepted types, a ValueError will be
662 raised.
663 """
664 global accept_mx
665 ## dbg(suspend=1)
666 ## dbg('TimeCtrl::GetWxDateTime(%s)' % repr(value), indent=1)
667 if value is None:
668 ## dbg('getting control value')
669 value = self.GetValue()
670 ## dbg('value = "%s"' % value)
671
672 if type(value) == types.UnicodeType:
673 value = str(value) # convert to regular string
674
675 valid = True # assume true
676 if type(value) == types.StringType:
677
678 # Construct constant wxDateTime, then try to parse the string:
679 wxdt = wx.DateTimeFromDMY(1, 0, 1970)
680 ## dbg('attempting conversion')
681 value = value.strip() # (parser doesn't like leading spaces)
682 checkTime = wxdt.ParseTime(value)
683 valid = checkTime == len(value) # entire string parsed?
684 ## dbg('checkTime == len(value)?', valid)
685
686 if not valid:
687 # deal with bug/deficiency in wx.DateTime:
688 if wxdt.Format('%p') not in ('AM', 'PM') and checkTime in (5,8):
689 # couldn't parse the AM/PM field
690 raise ValueError('cannot convert string "%s" to valid time for the current locale; please use 24hr time instead' % value)
691 else:
692 ## dbg(indent=0, suspend=0)
693 raise ValueError('cannot convert string "%s" to valid time' % value)
694
695 else:
696 if isinstance(value, wx.DateTime):
697 hour, minute, second = value.GetHour(), value.GetMinute(), value.GetSecond()
698 elif isinstance(value, wx.TimeSpan):
699 totalseconds = value.GetSeconds()
700 hour = totalseconds / 3600
701 minute = totalseconds / 60 - (hour * 60)
702 second = totalseconds - ((hour * 3600) + (minute * 60))
703
704 elif accept_mx and isinstance(value, DateTime.DateTimeType):
705 hour, minute, second = value.hour, value.minute, value.second
706 elif accept_mx and isinstance(value, DateTime.DateTimeDeltaType):
707 hour, minute, second = value.hour, value.minute, value.second
708 else:
709 # Not a valid function argument
710 if accept_mx:
711 error = 'GetWxDateTime requires wxDateTime, mxDateTime or parsable time string, passed %s'% repr(value)
712 else:
713 error = 'GetWxDateTime requires wxDateTime or parsable time string, passed %s'% repr(value)
714 ## dbg(indent=0, suspend=0)
715 raise ValueError(error)
716
717 wxdt = wx.DateTimeFromDMY(1, 0, 1970)
718 wxdt.SetHour(hour)
719 wxdt.SetMinute(minute)
720 wxdt.SetSecond(second)
721
722 ## dbg('wxdt:', wxdt, indent=0, suspend=0)
723 return wxdt
724
725
726 def SetMxDateTime(self, mxdt):
727 """
728 Because SetValue can take an mxDateTime, (if DateTime is importable),
729 this is now just an alias.
730 """
731 self.SetValue(value)
732
733
734 def GetMxDateTime(self, value=None):
735 if value is None:
736 t = self.GetValue(as_mxDateTime=True)
737 else:
738 # Convert string 1st to wxDateTime, then use components, since
739 # mx' DateTime.Parser.TimeFromString() doesn't handle AM/PM:
740 wxdt = self.GetWxDateTime(value)
741 hour, minute, second = wxdt.GetHour(), wxdt.GetMinute(), wxdt.GetSecond()
742 t = DateTime.DateTime(1970,1,1) + DateTimeDelta(0, hour, minute, second)
743 return t
744
745
746 def SetMin(self, min=None):
747 """
748 Sets the minimum value of the control. If a value of None
749 is provided, then the control will have no explicit minimum value.
750 If the value specified is greater than the current maximum value,
751 then the function returns 0 and the minimum will not change from
752 its current setting. On success, the function returns 1.
753
754 If successful and the current value is lower than the new lower
755 bound, if the control is limited, the value will be automatically
756 adjusted to the new minimum value; if not limited, the value in the
757 control will be colored as invalid.
758 """
759 ## dbg('TimeCtrl::SetMin(%s)'% repr(min), indent=1)
760 if min is not None:
761 try:
762 min = self.GetWxDateTime(min)
763 self.__min = self._toGUI(min)
764 except:
765 ## dbg('exception occurred', indent=0)
766 return False
767 else:
768 self.__min = min
769
770 if self.IsLimited() and not self.IsInBounds():
771 self.SetLimited(self.__limited) # force limited value:
772 else:
773 self._CheckValid()
774 ret = True
775 ## dbg('ret:', ret, indent=0)
776 return ret
777
778
779 def GetMin(self, as_string = False):
780 """
781 Gets the minimum value of the control.
782 If None, it will return None. Otherwise it will return
783 the current minimum bound on the control, as a wxDateTime
784 by default, or as a string if as_string argument is True.
785 """
786 ## dbg(suspend=1)
787 ## dbg('TimeCtrl::GetMin, as_string?', as_string, indent=1)
788 if self.__min is None:
789 ## dbg('(min == None)')
790 ret = self.__min
791 elif as_string:
792 ret = self.__min
793 ## dbg('ret:', ret)
794 else:
795 try:
796 ret = self.GetWxDateTime(self.__min)
797 except:
798 ## dbg(suspend=0)
799 ## dbg('exception occurred', indent=0)
800 raise
801 ## dbg('ret:', repr(ret))
802 ## dbg(indent=0, suspend=0)
803 return ret
804
805
806 def SetMax(self, max=None):
807 """
808 Sets the maximum value of the control. If a value of None
809 is provided, then the control will have no explicit maximum value.
810 If the value specified is less than the current minimum value, then
811 the function returns False and the maximum will not change from its
812 current setting. On success, the function returns True.
813
814 If successful and the current value is greater than the new upper
815 bound, if the control is limited the value will be automatically
816 adjusted to this maximum value; if not limited, the value in the
817 control will be colored as invalid.
818 """
819 ## dbg('TimeCtrl::SetMax(%s)' % repr(max), indent=1)
820 if max is not None:
821 try:
822 max = self.GetWxDateTime(max)
823 self.__max = self._toGUI(max)
824 except:
825 ## dbg('exception occurred', indent=0)
826 return False
827 else:
828 self.__max = max
829 ## dbg('max:', repr(self.__max))
830 if self.IsLimited() and not self.IsInBounds():
831 self.SetLimited(self.__limited) # force limited value:
832 else:
833 self._CheckValid()
834 ret = True
835 ## dbg('ret:', ret, indent=0)
836 return ret
837
838
839 def GetMax(self, as_string = False):
840 """
841 Gets the minimum value of the control.
842 If None, it will return None. Otherwise it will return
843 the current minimum bound on the control, as a wxDateTime
844 by default, or as a string if as_string argument is True.
845 """
846 ## dbg(suspend=1)
847 ## dbg('TimeCtrl::GetMin, as_string?', as_string, indent=1)
848 if self.__max is None:
849 ## dbg('(max == None)')
850 ret = self.__max
851 elif as_string:
852 ret = self.__max
853 ## dbg('ret:', ret)
854 else:
855 try:
856 ret = self.GetWxDateTime(self.__max)
857 except:
858 ## dbg(suspend=0)
859 ## dbg('exception occurred', indent=0)
860 raise
861 ## dbg('ret:', repr(ret))
862 ## dbg(indent=0, suspend=0)
863 return ret
864
865
866 def SetBounds(self, min=None, max=None):
867 """
868 This function is a convenience function for setting the min and max
869 values at the same time. The function only applies the maximum bound
870 if setting the minimum bound is successful, and returns True
871 only if both operations succeed.
872 NOTE: leaving out an argument will remove the corresponding bound.
873 """
874 ret = self.SetMin(min)
875 return ret and self.SetMax(max)
876
877
878 def GetBounds(self, as_string = False):
879 """
880 This function returns a two-tuple (min,max), indicating the
881 current bounds of the control. Each value can be None if
882 that bound is not set.
883 """
884 return (self.GetMin(as_string), self.GetMax(as_string))
885
886
887 def SetLimited(self, limited):
888 """
889 If called with a value of True, this function will cause the control
890 to limit the value to fall within the bounds currently specified.
891 If the control's value currently exceeds the bounds, it will then
892 be limited accordingly.
893
894 If called with a value of 0, this function will disable value
895 limiting, but coloring of out-of-bounds values will still take
896 place if bounds have been set for the control.
897 """
898 ## dbg('TimeCtrl::SetLimited(%d)' % limited, indent=1)
899 self.__limited = limited
900
901 if not limited:
902 self.SetMaskParameters(validRequired = False)
903 self._CheckValid()
904 ## dbg(indent=0)
905 return
906
907 ## dbg('requiring valid value')
908 self.SetMaskParameters(validRequired = True)
909
910 min = self.GetMin()
911 max = self.GetMax()
912 if min is None or max is None:
913 ## dbg('both bounds not set; no further action taken')
914 return # can't limit without 2 bounds
915
916 elif not self.IsInBounds():
917 # set value to the nearest bound:
918 try:
919 value = self.GetWxDateTime()
920 except:
921 ## dbg('exception occurred', indent=0)
922 raise
923
924 if min <= max: # valid range doesn't span midnight
925 ## dbg('min <= max')
926 # which makes the "nearest bound" computation trickier...
927
928 # determine how long the "invalid" pie wedge is, and cut
929 # this interval in half for comparison purposes:
930
931 # Note: relies on min and max and value date portions
932 # always being the same.
933 interval = (min + wx.TimeSpan(24, 0, 0, 0)) - max
934
935 half_interval = wx.TimeSpan(
936 0, # hours
937 0, # minutes
938 interval.GetSeconds() / 2, # seconds
939 0) # msec
940
941 if value < min: # min is on next day, so use value on
942 # "next day" for "nearest" interval calculation:
943 cmp_value = value + wx.TimeSpan(24, 0, 0, 0)
944 else: # "before midnight; ok
945 cmp_value = value
946
947 if (cmp_value - max) > half_interval:
948 ## dbg('forcing value to min (%s)' % min.FormatTime())
949 self.SetValue(min)
950 else:
951 ## dbg('forcing value to max (%s)' % max.FormatTime())
952 self.SetValue(max)
953 else:
954 ## dbg('max < min')
955 # therefore max < value < min guaranteed to be true,
956 # so "nearest bound" calculation is much easier:
957 if (value - max) >= (min - value):
958 # current value closer to min; pick that edge of pie wedge
959 ## dbg('forcing value to min (%s)' % min.FormatTime())
960 self.SetValue(min)
961 else:
962 ## dbg('forcing value to max (%s)' % max.FormatTime())
963 self.SetValue(max)
964
965 ## dbg(indent=0)
966
967
968
969 def IsLimited(self):
970 """
971 Returns True if the control is currently limiting the
972 value to fall within any current bounds. Note: can
973 be set even if there are no current bounds.
974 """
975 return self.__limited
976
977
978 def IsInBounds(self, value=None):
979 """
980 Returns True if no value is specified and the current value
981 of the control falls within the current bounds. As the clock
982 is a "circle", both minimum and maximum bounds must be set for
983 a value to ever be considered "out of bounds". This function can
984 also be called with a value to see if that value would fall within
985 the current bounds of the given control.
986 """
987 if value is not None:
988 try:
989 value = self.GetWxDateTime(value) # try to regularize passed value
990 except ValueError:
991 ## dbg('ValueError getting wxDateTime for %s' % repr(value), indent=0)
992 raise
993
994 ## dbg('TimeCtrl::IsInBounds(%s)' % repr(value), indent=1)
995 if self.__min is None or self.__max is None:
996 ## dbg(indent=0)
997 return True
998
999 elif value is None:
1000 try:
1001 value = self.GetWxDateTime()
1002 except:
1003 ## dbg('exception occurred', indent=0)
1004 raise
1005
1006 ## dbg('value:', value.FormatTime())
1007
1008 # Get wxDateTime representations of bounds:
1009 min = self.GetMin()
1010 max = self.GetMax()
1011
1012 midnight = wx.DateTimeFromDMY(1, 0, 1970)
1013 if min <= max: # they don't span midnight
1014 ret = min <= value <= max
1015
1016 else:
1017 # have to break into 2 tests; to be in bounds
1018 # either "min" <= value (<= midnight of *next day*)
1019 # or midnight <= value <= "max"
1020 ret = min <= value or (midnight <= value <= max)
1021 ## dbg('in bounds?', ret, indent=0)
1022 return ret
1023
1024
1025 def IsValid( self, value ):
1026 """
1027 Can be used to determine if a given value would be a legal and
1028 in-bounds value for the control.
1029 """
1030 try:
1031 self.__validateValue(value)
1032 return True
1033 except ValueError:
1034 return False
1035
1036 def SetFormat(self, format):
1037 self.SetParameters(format=format)
1038
1039 def GetFormat(self):
1040 if self.__displaySeconds:
1041 if self.__fmt24hr: return '24HHMMSS'
1042 else: return 'HHMMSS'
1043 else:
1044 if self.__fmt24hr: return '24HHMM'
1045 else: return 'HHMM'
1046
1047 #-------------------------------------------------------------------------------------------------------------
1048 # these are private functions and overrides:
1049
1050
1051 def __OnTextChange(self, event=None):
1052 ## dbg('TimeCtrl::OnTextChange', indent=1)
1053
1054 # Allow Maskedtext base control to color as appropriate,
1055 # and Skip the EVT_TEXT event (if appropriate.)
1056 ##! WS: For some inexplicable reason, every wxTextCtrl.SetValue()
1057 ## call is generating two (2) EVT_TEXT events. (!)
1058 ## The the only mechanism I can find to mask this problem is to
1059 ## keep track of last value seen, and declare a valid EVT_TEXT
1060 ## event iff the value has actually changed. The masked edit
1061 ## OnTextChange routine does this, and returns True on a valid event,
1062 ## False otherwise.
1063 if not BaseMaskedTextCtrl._OnTextChange(self, event):
1064 return
1065
1066 ## dbg('firing TimeUpdatedEvent...')
1067 evt = TimeUpdatedEvent(self.GetId(), self.GetValue())
1068 evt.SetEventObject(self)
1069 self.GetEventHandler().ProcessEvent(evt)
1070 ## dbg(indent=0)
1071
1072
1073 def SetInsertionPoint(self, pos):
1074 """
1075 Records the specified position and associated cell before calling base class' function.
1076 This is necessary to handle the optional spin button, because the insertion
1077 point is lost when the focus shifts to the spin button.
1078 """
1079 ## dbg('TimeCtrl::SetInsertionPoint', pos, indent=1)
1080 BaseMaskedTextCtrl.SetInsertionPoint(self, pos) # (causes EVT_TEXT event to fire)
1081 self.__posCurrent = self.GetInsertionPoint()
1082 ## dbg(indent=0)
1083
1084
1085 def SetSelection(self, sel_start, sel_to):
1086 ## dbg('TimeCtrl::SetSelection', sel_start, sel_to, indent=1)
1087
1088 # Adjust selection range to legal extent if not already
1089 if sel_start < 0:
1090 sel_start = 0
1091
1092 if self.__posCurrent != sel_start: # force selection and insertion point to match
1093 self.SetInsertionPoint(sel_start)
1094 cell_start, cell_end = self._FindField(sel_start)._extent
1095 if not cell_start <= sel_to <= cell_end:
1096 sel_to = cell_end
1097
1098 self.__bSelection = sel_start != sel_to
1099 BaseMaskedTextCtrl.SetSelection(self, sel_start, sel_to)
1100 ## dbg(indent=0)
1101
1102
1103 def __OnSpin(self, key):
1104 """
1105 This is the function that gets called in response to up/down arrow or
1106 bound spin button events.
1107 """
1108 self.__IncrementValue(key, self.__posCurrent) # changes the value
1109
1110 # Ensure adjusted control regains focus and has adjusted portion
1111 # selected:
1112 self.SetFocus()
1113 start, end = self._FindField(self.__posCurrent)._extent
1114 self.SetInsertionPoint(start)
1115 self.SetSelection(start, end)
1116 ## dbg('current position:', self.__posCurrent)
1117
1118
1119 def __OnSpinUp(self, event):
1120 """
1121 Event handler for any bound spin button on EVT_SPIN_UP;
1122 causes control to behave as if up arrow was pressed.
1123 """
1124 ## dbg('TimeCtrl::OnSpinUp', indent=1)
1125 self.__OnSpin(wx.WXK_UP)
1126 keep_processing = False
1127 ## dbg(indent=0)
1128 return keep_processing
1129
1130
1131 def __OnSpinDown(self, event):
1132 """
1133 Event handler for any bound spin button on EVT_SPIN_DOWN;
1134 causes control to behave as if down arrow was pressed.
1135 """
1136 ## dbg('TimeCtrl::OnSpinDown', indent=1)
1137 self.__OnSpin(wx.WXK_DOWN)
1138 keep_processing = False
1139 ## dbg(indent=0)
1140 return keep_processing
1141
1142
1143 def __OnChar(self, event):
1144 """
1145 Handler to explicitly look for ':' keyevents, and if found,
1146 clear the m_shiftDown field, so it will behave as forward tab.
1147 It then calls the base control's _OnChar routine with the modified
1148 event instance.
1149 """
1150 ## dbg('TimeCtrl::OnChar', indent=1)
1151 keycode = event.GetKeyCode()
1152 ## dbg('keycode:', keycode)
1153 if keycode == ord(':'):
1154 ## dbg('colon seen! removing shift attribute')
1155 event.m_shiftDown = False
1156 BaseMaskedTextCtrl._OnChar(self, event ) ## handle each keypress
1157 ## dbg(indent=0)
1158
1159
1160 def __OnSetToNow(self, event):
1161 """
1162 This is the key handler for '!' and 'c'; this allows the user to
1163 quickly set the value of the control to the current time.
1164 """
1165 self.SetValue(wx.DateTime_Now().FormatTime())
1166 keep_processing = False
1167 return keep_processing
1168
1169
1170 def __LimitSelection(self, event):
1171 """
1172 Event handler for motion events; this handler
1173 changes limits the selection to the new cell boundaries.
1174 """
1175 ## dbg('TimeCtrl::LimitSelection', indent=1)
1176 pos = self.GetInsertionPoint()
1177 self.__posCurrent = pos
1178 sel_start, sel_to = self.GetSelection()
1179 selection = sel_start != sel_to
1180 if selection:
1181 # only allow selection to end of current cell:
1182 start, end = self._FindField(sel_start)._extent
1183 if sel_to < pos: sel_to = start
1184 elif sel_to > pos: sel_to = end
1185
1186 ## dbg('new pos =', self.__posCurrent, 'select to ', sel_to)
1187 self.SetInsertionPoint(self.__posCurrent)
1188 self.SetSelection(self.__posCurrent, sel_to)
1189 if event: event.Skip()
1190 ## dbg(indent=0)
1191
1192
1193 def __IncrementValue(self, key, pos):
1194 ## dbg('TimeCtrl::IncrementValue', key, pos, indent=1)
1195 text = self.GetValue()
1196 field = self._FindField(pos)
1197 ## dbg('field: ', field._index)
1198 start, end = field._extent
1199 slice = text[start:end]
1200 if key == wx.WXK_UP: increment = 1
1201 else: increment = -1
1202
1203 if slice in ('A', 'P'):
1204 if slice == 'A': newslice = 'P'
1205 elif slice == 'P': newslice = 'A'
1206 newvalue = text[:start] + newslice + text[end:]
1207
1208 elif field._index == 0:
1209 # adjusting this field is trickier, as its value can affect the
1210 # am/pm setting. So, we use wxDateTime to generate a new value for us:
1211 # (Use a fixed date not subject to DST variations:)
1212 converter = wx.DateTimeFromDMY(1, 0, 1970)
1213 ## dbg('text: "%s"' % text)
1214 converter.ParseTime(text.strip())
1215 currenthour = converter.GetHour()
1216 ## dbg('current hour:', currenthour)
1217 newhour = (currenthour + increment) % 24
1218 ## dbg('newhour:', newhour)
1219 converter.SetHour(newhour)
1220 ## dbg('converter.GetHour():', converter.GetHour())
1221 newvalue = converter # take advantage of auto-conversion for am/pm in .SetValue()
1222
1223 else: # minute or second field; handled the same way:
1224 newslice = "%02d" % ((int(slice) + increment) % 60)
1225 newvalue = text[:start] + newslice + text[end:]
1226
1227 try:
1228 self.SetValue(newvalue)
1229
1230 except ValueError: # must not be in bounds:
1231 if not wx.Validator_IsSilent():
1232 wx.Bell()
1233 ## dbg(indent=0)
1234
1235
1236 def _toGUI( self, wxdt ):
1237 """
1238 This function takes a wxdt as an unambiguous representation of a time, and
1239 converts it to a string appropriate for the format of the control.
1240 """
1241 if self.__fmt24hr:
1242 if self.__displaySeconds: strval = wxdt.Format('%H:%M:%S')
1243 else: strval = wxdt.Format('%H:%M')
1244 else:
1245 if self.__displaySeconds: strval = wxdt.Format('%I:%M:%S %p')
1246 else: strval = wxdt.Format('%I:%M %p')
1247
1248 return strval
1249
1250
1251 def __validateValue( self, value ):
1252 """
1253 This function converts the value to a wxDateTime if not already one,
1254 does bounds checking and raises ValueError if argument is
1255 not a valid value for the control as currently specified.
1256 It is used by both the SetValue() and the IsValid() methods.
1257 """
1258 ## dbg('TimeCtrl::__validateValue(%s)' % repr(value), indent=1)
1259 if not value:
1260 ## dbg(indent=0)
1261 raise ValueError('%s not a valid time value' % repr(value))
1262
1263 valid = True # assume true
1264 try:
1265 value = self.GetWxDateTime(value) # regularize form; can generate ValueError if problem doing so
1266 except:
1267 ## dbg('exception occurred', indent=0)
1268 raise
1269
1270 if self.IsLimited() and not self.IsInBounds(value):
1271 ## dbg(indent=0)
1272 raise ValueError (
1273 'value %s is not within the bounds of the control' % str(value) )
1274 ## dbg(indent=0)
1275 return value
1276
1277 #----------------------------------------------------------------------------
1278 # Test jig for TimeCtrl:
1279
1280 if __name__ == '__main__':
1281 import traceback
1282
1283 class TestPanel(wx.Panel):
1284 def __init__(self, parent, id,
1285 pos = wx.DefaultPosition, size = wx.DefaultSize,
1286 fmt24hr = 0, test_mx = 0,
1287 style = wx.TAB_TRAVERSAL ):
1288
1289 wx.Panel.__init__(self, parent, id, pos, size, style)
1290
1291 self.test_mx = test_mx
1292
1293 self.tc = TimeCtrl(self, 10, fmt24hr = fmt24hr)
1294 sb = wx.SpinButton( self, 20, wx.DefaultPosition, (-1,20), 0 )
1295 self.tc.BindSpinButton(sb)
1296
1297 sizer = wx.BoxSizer( wx.HORIZONTAL )
1298 sizer.Add( self.tc, 0, wx.ALIGN_CENTRE|wx.LEFT|wx.TOP|wx.BOTTOM, 5 )
1299 sizer.Add( sb, 0, wx.ALIGN_CENTRE|wx.RIGHT|wx.TOP|wx.BOTTOM, 5 )
1300
1301 self.SetAutoLayout( True )
1302 self.SetSizer( sizer )
1303 sizer.Fit( self )
1304 sizer.SetSizeHints( self )
1305
1306 self.Bind(EVT_TIMEUPDATE, self.OnTimeChange, self.tc)
1307
1308 def OnTimeChange(self, event):
1309 ## dbg('OnTimeChange: value = ', event.GetValue())
1310 wxdt = self.tc.GetWxDateTime()
1311 ## dbg('wxdt =', wxdt.GetHour(), wxdt.GetMinute(), wxdt.GetSecond())
1312 if self.test_mx:
1313 mxdt = self.tc.GetMxDateTime()
1314 ## dbg('mxdt =', mxdt.hour, mxdt.minute, mxdt.second)
1315
1316
1317 class MyApp(wx.App):
1318 def OnInit(self):
1319 import sys
1320 fmt24hr = '24' in sys.argv
1321 test_mx = 'mx' in sys.argv
1322 try:
1323 frame = wx.Frame(None, -1, "TimeCtrl Test", (20,20), (100,100) )
1324 panel = TestPanel(frame, -1, (-1,-1), fmt24hr=fmt24hr, test_mx = test_mx)
1325 frame.Show(True)
1326 except:
1327 traceback.print_exc()
1328 return False
1329 return True
1330
1331 try:
1332 app = MyApp(0)
1333 app.MainLoop()
1334 except:
1335 traceback.print_exc()
1336 i=0
1337 ## Version 1.2
1338 ## 1. Changed parameter name display_seconds to displaySeconds, to follow
1339 ## other masked edit conventions.
1340 ## 2. Added format parameter, to remove need to use both fmt24hr and displaySeconds.
1341 ## 3. Changed inheritance to use BaseMaskedTextCtrl, to remove exposure of
1342 ## nonsensical parameter methods from the control, so it will work
1343 ## properly with Boa.