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