]>
git.saurik.com Git - wxWidgets.git/blob - wxPython/wx/lib/timectrl.py
1 #----------------------------------------------------------------------------
5 # Copyright: (c) 2002 by Will Sadkin, 2002
7 # License: wxWindows license
8 #----------------------------------------------------------------------------
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
16 # wxTimeCtrl 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.
21 # wxTimeCtrl 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.
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)
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
43 <B>wxTimeCtrl</B> provides a multi-cell control that allows manipulation of a time
44 value. It supports 12 or 24 hour format, and you can use wxDateTime or mxDateTime
45 to get/set values from the control.
47 Left/right/tab keys to switch cells within a wxTimeCtrl, and the up/down arrows act
48 like a spin control. wxTimeCtrl also allows for an actual spin button to be attached
49 to the control, so that it acts like the up/down arrow keys.
51 The <B>!</B> or <B>c</B> key sets the value of the control to the current time.
53 Here's the API for wxTimeCtrl:
57 <B>value</B> = '12:00:00 AM',
58 pos = wxDefaultPosition,
60 <B>style</B> = wxTE_PROCESS_TAB,
61 <B>validator</B> = wxDefaultValidator,
63 <B>fmt24hr</B> = False,
64 <B>spinButton</B> = None,
67 <B>limited</B> = None,
68 <B>oob_color</B> = "Yellow"
73 <DD>If no initial value is set, the default will be midnight; if an illegal string
74 is specified, a ValueError will result. (You can always later set the initial time
75 with SetValue() after instantiation of the control.)
77 <DD>The size of the control will be automatically adjusted for 12/24 hour format
78 if wxDefaultSize is specified.
80 <DD>By default, wxTimeCtrl will process TAB events, by allowing tab to the
81 different cells within the control.
83 <DD>By default, wxTimeCtrl just uses the default (empty) validator, as all
84 of its validation for entry control is handled internally. However, a validator
85 can be supplied to provide data transfer capability to the control.
88 <DD>If True, control will display time in 24 hour time format; if False, it will
89 use 12 hour AM/PM format. SetValue() will adjust values accordingly for the
90 control, based on the format specified.
93 <DD>If specified, this button's events will be bound to the behavior of the
94 wxTimeCtrl, working like up/down cursor key events. (See BindSpinButton.)
97 <DD>Defines the lower bound for "valid" selections in the control.
98 By default, wxTimeCtrl doesn't have bounds. You must set both upper and lower
99 bounds to make the control pay attention to them, (as only one bound makes no sense
100 with times.) "Valid" times will fall between the min and max "pie wedge" of the
103 <DD>Defines the upper bound for "valid" selections in the control.
104 "Valid" times will fall between the min and max "pie wedge" of the
105 clock. (This can be a "big piece", ie. <b>min = 11pm, max= 10pm</b>
106 means <I>all but the hour from 10:00pm to 11pm are valid times.</I>)
108 <DD>If True, the control will not permit entry of values that fall outside the
112 <DD>Sets the background color used to indicate out-of-bounds values for the control
113 when the control is not limited. This is set to "Yellow" by default.
119 <DT><B>EVT_TIMEUPDATE(win, id, func)</B>
120 <DD>func is fired whenever the value of the control changes.
123 <DT><B>SetValue(time_string | wxDateTime | wxTimeSpan | mx.DateTime | mx.DateTimeDelta)</B>
124 <DD>Sets the value of the control to a particular time, given a valid
125 value; raises ValueError on invalid value.
126 <EM>NOTE:</EM> This will only allow mx.DateTime or mx.DateTimeDelta if mx.DateTime
127 was successfully imported by the class module.
129 <DT><B>GetValue(as_wxDateTime = False, as_mxDateTime = False, as_wxTimeSpan=False, as mxDateTimeDelta=False)</B>
130 <DD>Retrieves the value of the time from the control. By default this is
131 returned as a string, unless one of the other arguments is set; args are
132 searched in the order listed; only one value will be returned.
134 <DT><B>GetWxDateTime(value=None)</B>
135 <DD>When called without arguments, retrieves the value of the control, and applies
136 it to the wxDateTimeFromHMS() constructor, and returns the resulting value.
137 The date portion will always be set to Jan 1, 1970. This form is the same
138 as GetValue(as_wxDateTime=True). GetWxDateTime can also be called with any of the
139 other valid time formats settable with SetValue, to regularize it to a single
140 wxDateTime form. The function will raise ValueError on an unconvertable argument.
142 <DT><B>GetMxDateTime()</B>
143 <DD>Retrieves the value of the control and applies it to the DateTime.Time()
144 constructor,and returns the resulting value. (The date portion will always be
145 set to Jan 1, 1970.) (Same as GetValue(as_wxDateTime=True); provided for backward
146 compatibility with previous release.)
149 <DT><B>BindSpinButton(wxSpinBtton)</B>
150 <DD>Binds an externally created spin button to the control, so that up/down spin
151 events change the active cell or selection in the control (in addition to the
152 up/down cursor keys.) (This is primarily to allow you to create a "standard"
153 interface to time controls, as seen in Windows.)
156 <DT><B>SetMin(min=None)</B>
157 <DD>Sets the expected minimum value, or lower bound, of the control.
158 (The lower bound will only be enforced if the control is
159 configured to limit its values to the set bounds.)
160 If a value of <I>None</I> is provided, then the control will have
161 explicit lower bound. If the value specified is greater than
162 the current lower bound, then the function returns False and the
163 lower bound will not change from its current setting. On success,
164 the function returns True. Even if set, if there is no corresponding
165 upper bound, the control will behave as if it is unbounded.
166 <DT><DD>If successful and the current value is outside the
167 new bounds, if the control is limited the value will be
168 automatically adjusted to the nearest bound; if not limited,
169 the background of the control will be colored with the current
172 <DT><B>GetMin(as_string=False)</B>
173 <DD>Gets the current lower bound value for the control, returning
174 None, if not set, or a wxDateTime, unless the as_string parameter
175 is set to True, at which point it will return the string
176 representation of the lower bound.
179 <DT><B>SetMax(max=None)</B>
180 <DD>Sets the expected maximum value, or upper bound, of the control.
181 (The upper bound will only be enforced if the control is
182 configured to limit its values to the set bounds.)
183 If a value of <I>None</I> is provided, then the control will
184 have no explicit upper bound. If the value specified is less
185 than the current lower bound, then the function returns False and
186 the maximum will not change from its current setting. On success,
187 the function returns True. Even if set, if there is no corresponding
188 lower bound, the control will behave as if it is unbounded.
189 <DT><DD>If successful and the current value is outside the
190 new bounds, if the control is limited the value will be
191 automatically adjusted to the nearest bound; if not limited,
192 the background of the control will be colored with the current
195 <DT><B>GetMax(as_string = False)</B>
196 <DD>Gets the current upper bound value for the control, returning
197 None, if not set, or a wxDateTime, unless the as_string parameter
198 is set to True, at which point it will return the string
199 representation of the lower bound.
203 <DT><B>SetBounds(min=None,max=None)</B>
204 <DD>This function is a convenience function for setting the min and max
205 values at the same time. The function only applies the maximum bound
206 if setting the minimum bound is successful, and returns True
207 only if both operations succeed. <B><I>Note: leaving out an argument
208 will remove the corresponding bound, and result in the behavior of
209 an unbounded control.</I></B>
211 <DT><B>GetBounds(as_string = False)</B>
212 <DD>This function returns a two-tuple (min,max), indicating the
213 current bounds of the control. Each value can be None if
214 that bound is not set. The values will otherwise be wxDateTimes
215 unless the as_string argument is set to True, at which point they
216 will be returned as string representations of the bounds.
219 <DT><B>IsInBounds(value=None)</B>
220 <DD>Returns <I>True</I> if no value is specified and the current value
221 of the control falls within the current bounds. This function can also
222 be called with a value to see if that value would fall within the current
223 bounds of the given control. It will raise ValueError if the value
224 specified is not a wxDateTime, mxDateTime (if available) or parsable string.
227 <DT><B>IsValid(value)</B>
228 <DD>Returns <I>True</I>if specified value is a legal time value and
229 falls within the current bounds of the given control.
232 <DT><B>SetLimited(bool)</B>
233 <DD>If called with a value of True, this function will cause the control
234 to limit the value to fall within the bounds currently specified.
235 (Provided both bounds have been set.)
236 If the control's value currently exceeds the bounds, it will then
237 be set to the nearest bound.
238 If called with a value of False, this function will disable value
239 limiting, but coloring of out-of-bounds values will still take
240 place if bounds have been set for the control.
241 <DT><B>IsLimited()</B>
242 <DD>Returns <I>True</I> if the control is currently limiting the
243 value to fall within the current bounds.
255 from wx
.tools
.dbg
import Logger
256 from wx
.lib
.maskededit
import wxMaskedTextCtrl
, Field
262 from mx
import DateTime
267 # This class of event fires whenever the value of the time changes in the control:
268 wxEVT_TIMEVAL_UPDATED
= wx
.NewEventType()
269 EVT_TIMEUPDATE
= wx
.PyEventBinder(wxEVT_TIMEVAL_UPDATED
, 1)
271 class TimeUpdatedEvent(wx
.PyCommandEvent
):
272 def __init__(self
, id, value
='12:00:00 AM'):
273 wx
.PyCommandEvent
.__init
__(self
, wxEVT_TIMEVAL_UPDATED
, id)
276 """Retrieve the value of the time control at the time this event was generated"""
280 class wxTimeCtrl(wxMaskedTextCtrl
):
282 valid_ctrl_params
= {
283 'display_seconds' : True, # by default, shows seconds
284 'min': None, # by default, no bounds set
286 'limited': False, # by default, no limiting even if bounds set
287 'useFixedWidthFont': True, # by default, use a fixed-width font
288 'oob_color': "Yellow" # by default, the default wxMaskedTextCtrl "invalid" color
292 self
, parent
, id=-1, value
= '12:00:00 AM',
293 pos
= wx
.DefaultPosition
, size
= wx
.DefaultSize
,
296 style
= wx
.TE_PROCESS_TAB
,
297 validator
= wx
.DefaultValidator
,
301 # set defaults for control:
302 dbg('setting defaults:')
303 for key
, param_value
in wxTimeCtrl
.valid_ctrl_params
.items():
304 # This is done this way to make setattr behave consistently with
305 # "private attribute" name mangling
306 setattr(self
, "_wxTimeCtrl__" + key
, copy
.copy(param_value
))
308 # create locals from current defaults, so we can override if
309 # specified in kwargs, and handle uniformly:
312 limited
= self
.__limited
313 self
.__posCurrent
= 0
316 # (handle positional args (from original release) differently from rest of kwargs:)
317 self
.__fmt
24hr
= fmt24hr
319 maskededit_kwargs
= {}
321 # assign keyword args as appropriate:
322 for key
, param_value
in kwargs
.items():
323 if key
not in wxTimeCtrl
.valid_ctrl_params
.keys():
324 raise AttributeError('invalid keyword argument "%s"' % key
)
326 if key
== "display_seconds":
327 self
.__display
_seconds
= param_value
329 elif key
== "min": min = param_value
330 elif key
== "max": max = param_value
331 elif key
== "limited": limited
= param_value
333 elif key
== "useFixedWidthFont":
334 maskededit_kwargs
[key
] = param_value
335 elif key
== "oob_color":
336 maskededit_kwargs
['invalidBackgroundColor'] = param_value
339 if self
.__display
_seconds
: maskededit_kwargs
['autoformat'] = 'MILTIMEHHMMSS'
340 else: maskededit_kwargs
['autoformat'] = 'MILTIMEHHMM'
342 # Set hour field to zero-pad, right-insert, require explicit field change,
343 # select entire field on entry, and require a resultant valid entry
344 # to allow character entry:
345 hourfield
= Field(formatcodes
='0r<SV', validRegex
='0\d|1\d|2[0123]', validRequired
=True)
347 if self
.__display
_seconds
: maskededit_kwargs
['autoformat'] = 'TIMEHHMMSS'
348 else: maskededit_kwargs
['autoformat'] = 'TIMEHHMM'
350 # Set hour field to allow spaces (at start), right-insert,
351 # require explicit field change, select entire field on entry,
352 # and require a resultant valid entry to allow character entry:
353 hourfield
= Field(formatcodes
='_0<rSV', validRegex
='0[1-9]| [1-9]|1[012]', validRequired
=True)
354 ampmfield
= Field(formatcodes
='S', emptyInvalid
= True, validRequired
= True)
356 # Field 1 is always a zero-padded right-insert minute field,
357 # similarly configured as above:
358 minutefield
= Field(formatcodes
='0r<SV', validRegex
='[0-5]\d', validRequired
=True)
360 fields
= [ hourfield
, minutefield
]
361 if self
.__display
_seconds
:
362 fields
.append(copy
.copy(minutefield
)) # second field has same constraints as field 1
364 if not self
.__fmt
24hr
:
365 fields
.append(ampmfield
)
367 # set fields argument:
368 maskededit_kwargs
['fields'] = fields
370 # This allows range validation if set
371 maskededit_kwargs
['validFunc'] = self
.IsInBounds
373 # This allows range limits to affect insertion into control or not
374 # dynamically without affecting individual field constraint validation
375 maskededit_kwargs
['retainFieldValidation'] = True
377 # allow control over font selection:
378 maskededit_kwargs
['useFixedWidthFont'] = self
.__useFixedWidthFont
380 # allow for explicit size specification:
381 if size
!= wx
.DefaultSize
:
382 # override (and remove) "autofit" autoformat code in standard time formats:
383 maskededit_kwargs
['formatcodes'] = 'T!'
385 # Now we can initialize the base control:
386 wxMaskedTextCtrl
.__init
__(
390 validator
= validator
,
392 setupEventHandling
= False,
396 # This makes ':' act like tab (after we fix each ':' key event to remove "shift")
397 self
._SetKeyHandler
(':', self
._OnChangeField
)
400 # This makes the up/down keys act like spin button controls:
401 self
._SetKeycodeHandler
(wx
.WXK_UP
, self
.__OnSpinUp
)
402 self
._SetKeycodeHandler
(wx
.WXK_DOWN
, self
.__OnSpinDown
)
405 # This allows ! and c/C to set the control to the current time:
406 self
._SetKeyHandler
('!', self
.__OnSetToNow
)
407 self
._SetKeyHandler
('c', self
.__OnSetToNow
)
408 self
._SetKeyHandler
('C', self
.__OnSetToNow
)
411 # Set up event handling ourselves, so we can insert special
412 # processing on the ":' key to remove the "shift" attribute
413 # *before* the default handlers have been installed, so
414 # that : takes you forward, not back, and so we can issue
415 # EVT_TIMEUPDATE events on changes:
417 self
.Bind(wx
.EVT_SET_FOCUS
, self
._OnFocus
) ## defeat automatic full selection
418 self
.Bind(wx
.EVT_KILL_FOCUS
, self
._OnKillFocus
) ## run internal validator
419 self
.Bind(wx
.EVT_LEFT_UP
, self
.__LimitSelection
) ## limit selections to single field
420 self
.Bind(wx
.EVT_LEFT_DCLICK
, self
._OnDoubleClick
) ## select field under cursor on dclick
421 self
.Bind(wx
.EVT_KEY_DOWN
, self
._OnKeyDown
) ## capture control events not normally seen, eg ctrl-tab.
422 self
.Bind(wx
.EVT_CHAR
, self
.__OnChar
) ## remove "shift" attribute from colon key event,
423 ## then call wxMaskedTextCtrl._OnChar with
424 ## the possibly modified event.
425 self
.Bind(wx
.EVT_TEXT
, self
.__OnTextChange
, self
) ## color control appropriately and EVT_TIMEUPDATE events
428 # Validate initial value and set if appropriate
430 self
.SetBounds(min, max)
431 self
.SetLimited(limited
)
434 self
.SetValue('12:00:00 AM')
437 self
.BindSpinButton(spinButton
) # bind spin button up/down events to this control
441 def BindSpinButton(self
, sb
):
443 This function binds an externally created spin button to the control, so that
444 up/down events from the button automatically change the control.
446 dbg('wxTimeCtrl::BindSpinButton')
447 self
.__spinButton
= sb
448 if self
.__spinButton
:
449 # bind event handlers to spin ctrl
450 self
.__spinButton
.Bind(wx
.EVT_SPIN_UP
, self
.__OnSpinUp
, self
.__spinButton
)
451 self
.__spinButton
.Bind(wx
.EVT_SPIN_DOWN
, self
.__OnSpinDown
, self
.__spinButton
)
455 return "<wxTimeCtrl: %s>" % self
.GetValue()
458 def SetValue(self
, value
):
460 Validating SetValue function for time values:
461 This function will do dynamic type checking on the value argument,
462 and convert wxDateTime, mxDateTime, or 12/24 format time string
463 into the appropriate format string for the control.
465 dbg('wxTimeCtrl::SetValue(%s)' % repr(value
), indent
=1)
467 strtime
= self
._toGUI
(self
.__validateValue
(value
))
469 dbg('validation failed', indent
=0)
472 dbg('strtime:', strtime
)
473 self
._SetValue
(strtime
)
477 as_wxDateTime
= False,
478 as_mxDateTime
= False,
479 as_wxTimeSpan
= False,
480 as_mxDateTimeDelta
= False):
483 if as_wxDateTime
or as_mxDateTime
or as_wxTimeSpan
or as_mxDateTimeDelta
:
484 value
= self
.GetWxDateTime()
488 value
= DateTime
.DateTime(1970, 1, 1, value
.GetHour(), value
.GetMinute(), value
.GetSecond())
490 value
= wx
.TimeSpan(value
.GetHour(), value
.GetMinute(), value
.GetSecond())
491 elif as_mxDateTimeDelta
:
492 value
= DateTime
.DateTimeDelta(0, value
.GetHour(), value
.GetMinute(), value
.GetSecond())
494 value
= wxMaskedTextCtrl
.GetValue(self
)
498 def SetWxDateTime(self
, wxdt
):
500 Because SetValue can take a wxDateTime, this is now just an alias.
505 def GetWxDateTime(self
, value
=None):
507 This function is the conversion engine for wxTimeCtrl; it takes
508 one of the following types:
514 and converts it to a wxDateTime that always has Jan 1, 1970 as its date
515 portion, so that range comparisons around values can work using
516 wxDateTime's built-in comparison function. If a value is not
517 provided to convert, the string value of the control will be used.
518 If the value is not one of the accepted types, a ValueError will be
523 dbg('wxTimeCtrl::GetWxDateTime(%s)' % repr(value
), indent
=1)
525 dbg('getting control value')
526 value
= self
.GetValue()
527 dbg('value = "%s"' % value
)
529 if type(value
) == types
.UnicodeType
:
530 value
= str(value
) # convert to regular string
532 valid
= True # assume true
533 if type(value
) == types
.StringType
:
535 # Construct constant wxDateTime, then try to parse the string:
536 wxdt
= wx
.DateTimeFromDMY(1, 0, 1970)
537 dbg('attempting conversion')
538 value
= value
.strip() # (parser doesn't like leading spaces)
539 checkTime
= wxdt
.ParseTime(value
)
540 valid
= checkTime
== len(value
) # entire string parsed?
541 dbg('checkTime == len(value)?', valid
)
544 dbg(indent
=0, suspend
=0)
545 raise ValueError('cannot convert string "%s" to valid time' % value
)
548 if isinstance(value
, wx
.DateTime
):
549 hour
, minute
, second
= value
.GetHour(), value
.GetMinute(), value
.GetSecond()
550 elif isinstance(value
, wx
.TimeSpan
):
551 totalseconds
= value
.GetSeconds()
552 hour
= totalseconds
/ 3600
553 minute
= totalseconds
/ 60 - (hour
* 60)
554 second
= totalseconds
- ((hour
* 3600) + (minute
* 60))
556 elif accept_mx
and isinstance(value
, DateTime
.DateTimeType
):
557 hour
, minute
, second
= value
.hour
, value
.minute
, value
.second
558 elif accept_mx
and isinstance(value
, DateTime
.DateTimeDeltaType
):
559 hour
, minute
, second
= value
.hour
, value
.minute
, value
.second
561 # Not a valid function argument
563 error
= 'GetWxDateTime requires wxDateTime, mxDateTime or parsable time string, passed %s'% repr(value
)
565 error
= 'GetWxDateTime requires wxDateTime or parsable time string, passed %s'% repr(value
)
566 dbg(indent
=0, suspend
=0)
567 raise ValueError(error
)
569 wxdt
= wx
.DateTimeFromDMY(1, 0, 1970)
571 wxdt
.SetMinute(minute
)
572 wxdt
.SetSecond(second
)
574 dbg('wxdt:', wxdt
, indent
=0, suspend
=0)
578 def SetMxDateTime(self
, mxdt
):
580 Because SetValue can take an mxDateTime, (if DateTime is importable),
581 this is now just an alias.
586 def GetMxDateTime(self
, value
=None):
588 t
= self
.GetValue(as_mxDateTime
=True)
590 # Convert string 1st to wxDateTime, then use components, since
591 # mx' DateTime.Parser.TimeFromString() doesn't handle AM/PM:
592 wxdt
= self
.GetWxDateTime(value
)
593 hour
, minute
, second
= wxdt
.GetHour(), wxdt
.GetMinute(), wxdt
.GetSecond()
594 t
= DateTime
.DateTime(1970,1,1) + DateTimeDelta(0, hour
, minute
, second
)
598 def SetMin(self
, min=None):
600 Sets the minimum value of the control. If a value of None
601 is provided, then the control will have no explicit minimum value.
602 If the value specified is greater than the current maximum value,
603 then the function returns 0 and the minimum will not change from
604 its current setting. On success, the function returns 1.
606 If successful and the current value is lower than the new lower
607 bound, if the control is limited, the value will be automatically
608 adjusted to the new minimum value; if not limited, the value in the
609 control will be colored as invalid.
611 dbg('wxTimeCtrl::SetMin(%s)'% repr(min), indent
=1)
614 min = self
.GetWxDateTime(min)
615 self
.__min
= self
._toGUI
(min)
617 dbg('exception occurred', indent
=0)
622 if self
.IsLimited() and not self
.IsInBounds():
623 self
.SetLimited(self
.__limited
) # force limited value:
627 dbg('ret:', ret
, indent
=0)
631 def GetMin(self
, as_string
= False):
633 Gets the minimum value of the control.
634 If None, it will return None. Otherwise it will return
635 the current minimum bound on the control, as a wxDateTime
636 by default, or as a string if as_string argument is True.
639 dbg('wxTimeCtrl::GetMin, as_string?', as_string
, indent
=1)
640 if self
.__min
is None:
648 ret
= self
.GetWxDateTime(self
.__min
)
651 dbg('exception occurred', indent
=0)
652 dbg('ret:', repr(ret
))
653 dbg(indent
=0, suspend
=0)
657 def SetMax(self
, max=None):
659 Sets the maximum value of the control. If a value of None
660 is provided, then the control will have no explicit maximum value.
661 If the value specified is less than the current minimum value, then
662 the function returns False and the maximum will not change from its
663 current setting. On success, the function returns True.
665 If successful and the current value is greater than the new upper
666 bound, if the control is limited the value will be automatically
667 adjusted to this maximum value; if not limited, the value in the
668 control will be colored as invalid.
670 dbg('wxTimeCtrl::SetMax(%s)' % repr(max), indent
=1)
673 max = self
.GetWxDateTime(max)
674 self
.__max
= self
._toGUI
(max)
676 dbg('exception occurred', indent
=0)
680 dbg('max:', repr(self
.__max
))
681 if self
.IsLimited() and not self
.IsInBounds():
682 self
.SetLimited(self
.__limited
) # force limited value:
686 dbg('ret:', ret
, indent
=0)
690 def GetMax(self
, as_string
= False):
692 Gets the minimum value of the control.
693 If None, it will return None. Otherwise it will return
694 the current minimum bound on the control, as a wxDateTime
695 by default, or as a string if as_string argument is True.
698 dbg('wxTimeCtrl::GetMin, as_string?', as_string
, indent
=1)
699 if self
.__max
is None:
707 ret
= self
.GetWxDateTime(self
.__max
)
710 dbg('exception occurred', indent
=0)
712 dbg('ret:', repr(ret
))
713 dbg(indent
=0, suspend
=0)
717 def SetBounds(self
, min=None, max=None):
719 This function is a convenience function for setting the min and max
720 values at the same time. The function only applies the maximum bound
721 if setting the minimum bound is successful, and returns True
722 only if both operations succeed.
723 NOTE: leaving out an argument will remove the corresponding bound.
725 ret
= self
.SetMin(min)
726 return ret
and self
.SetMax(max)
729 def GetBounds(self
, as_string
= False):
731 This function returns a two-tuple (min,max), indicating the
732 current bounds of the control. Each value can be None if
733 that bound is not set.
735 return (self
.GetMin(as_string
), self
.GetMax(as_string
))
738 def SetLimited(self
, limited
):
740 If called with a value of True, this function will cause the control
741 to limit the value to fall within the bounds currently specified.
742 If the control's value currently exceeds the bounds, it will then
743 be limited accordingly.
745 If called with a value of 0, this function will disable value
746 limiting, but coloring of out-of-bounds values will still take
747 place if bounds have been set for the control.
749 dbg('wxTimeCtrl::SetLimited(%d)' % limited
, indent
=1)
750 self
.__limited
= limited
753 self
.SetMaskParameters(validRequired
= False)
758 dbg('requiring valid value')
759 self
.SetMaskParameters(validRequired
= True)
763 if min is None or max is None:
764 dbg('both bounds not set; no further action taken')
765 return # can't limit without 2 bounds
767 elif not self
.IsInBounds():
768 # set value to the nearest bound:
770 value
= self
.GetWxDateTime()
772 dbg('exception occurred', indent
=0)
775 if min <= max: # valid range doesn't span midnight
777 # which makes the "nearest bound" computation trickier...
779 # determine how long the "invalid" pie wedge is, and cut
780 # this interval in half for comparison purposes:
782 # Note: relies on min and max and value date portions
783 # always being the same.
784 interval
= (min + wx
.TimeSpan(24, 0, 0, 0)) - max
786 half_interval
= wx
.TimeSpan(
789 interval
.GetSeconds() / 2, # seconds
792 if value
< min: # min is on next day, so use value on
793 # "next day" for "nearest" interval calculation:
794 cmp_value
= value
+ wx
.TimeSpan(24, 0, 0, 0)
795 else: # "before midnight; ok
798 if (cmp_value
- max) > half_interval
:
799 dbg('forcing value to min (%s)' % min.FormatTime())
802 dbg('forcing value to max (%s)' % max.FormatTime())
806 # therefore max < value < min guaranteed to be true,
807 # so "nearest bound" calculation is much easier:
808 if (value
- max) >= (min - value
):
809 # current value closer to min; pick that edge of pie wedge
810 dbg('forcing value to min (%s)' % min.FormatTime())
813 dbg('forcing value to max (%s)' % max.FormatTime())
822 Returns True if the control is currently limiting the
823 value to fall within any current bounds. Note: can
824 be set even if there are no current bounds.
826 return self
.__limited
829 def IsInBounds(self
, value
=None):
831 Returns True if no value is specified and the current value
832 of the control falls within the current bounds. As the clock
833 is a "circle", both minimum and maximum bounds must be set for
834 a value to ever be considered "out of bounds". This function can
835 also be called with a value to see if that value would fall within
836 the current bounds of the given control.
838 if value
is not None:
840 value
= self
.GetWxDateTime(value
) # try to regularize passed value
842 dbg('ValueError getting wxDateTime for %s' % repr(value
), indent
=0)
845 dbg('wxTimeCtrl::IsInBounds(%s)' % repr(value
), indent
=1)
846 if self
.__min
is None or self
.__max
is None:
852 value
= self
.GetWxDateTime()
854 dbg('exception occurred', indent
=0)
856 dbg('value:', value
.FormatTime())
858 # Get wxDateTime representations of bounds:
862 midnight
= wx
.DateTimeFromDMY(1, 0, 1970)
863 if min <= max: # they don't span midnight
864 ret
= min <= value
<= max
867 # have to break into 2 tests; to be in bounds
868 # either "min" <= value (<= midnight of *next day*)
869 # or midnight <= value <= "max"
870 ret
= min <= value
or (midnight
<= value
<= max)
871 dbg('in bounds?', ret
, indent
=0)
875 def IsValid( self
, value
):
877 Can be used to determine if a given value would be a legal and
878 in-bounds value for the control.
881 self
.__validateValue
(value
)
887 #-------------------------------------------------------------------------------------------------------------
888 # these are private functions and overrides:
891 def __OnTextChange(self
, event
=None):
892 dbg('wxTimeCtrl::OnTextChange', indent
=1)
894 # Allow wxMaskedtext base control to color as appropriate,
895 # and Skip the EVT_TEXT event (if appropriate.)
896 ##! WS: For some inexplicable reason, every wxTextCtrl.SetValue()
897 ## call is generating two (2) EVT_TEXT events. (!)
898 ## The the only mechanism I can find to mask this problem is to
899 ## keep track of last value seen, and declare a valid EVT_TEXT
900 ## event iff the value has actually changed. The masked edit
901 ## OnTextChange routine does this, and returns True on a valid event,
903 if not wxMaskedTextCtrl
._OnTextChange
(self
, event
):
906 dbg('firing TimeUpdatedEvent...')
907 evt
= TimeUpdatedEvent(self
.GetId(), self
.GetValue())
908 evt
.SetEventObject(self
)
909 self
.GetEventHandler().ProcessEvent(evt
)
913 def SetInsertionPoint(self
, pos
):
915 Records the specified position and associated cell before calling base class' function.
916 This is necessary to handle the optional spin button, because the insertion
917 point is lost when the focus shifts to the spin button.
919 dbg('wxTimeCtrl::SetInsertionPoint', pos
, indent
=1)
920 wxMaskedTextCtrl
.SetInsertionPoint(self
, pos
) # (causes EVT_TEXT event to fire)
921 self
.__posCurrent
= self
.GetInsertionPoint()
925 def SetSelection(self
, sel_start
, sel_to
):
926 dbg('wxTimeCtrl::SetSelection', sel_start
, sel_to
, indent
=1)
928 # Adjust selection range to legal extent if not already
932 if self
.__posCurrent
!= sel_start
: # force selection and insertion point to match
933 self
.SetInsertionPoint(sel_start
)
934 cell_start
, cell_end
= self
._FindField
(sel_start
)._extent
935 if not cell_start
<= sel_to
<= cell_end
:
938 self
.__bSelection
= sel_start
!= sel_to
939 wxMaskedTextCtrl
.SetSelection(self
, sel_start
, sel_to
)
943 def __OnSpin(self
, key
):
945 This is the function that gets called in response to up/down arrow or
946 bound spin button events.
948 self
.__IncrementValue
(key
, self
.__posCurrent
) # changes the value
950 # Ensure adjusted control regains focus and has adjusted portion
953 start
, end
= self
._FindField
(self
.__posCurrent
)._extent
954 self
.SetInsertionPoint(start
)
955 self
.SetSelection(start
, end
)
956 dbg('current position:', self
.__posCurrent
)
959 def __OnSpinUp(self
, event
):
961 Event handler for any bound spin button on EVT_SPIN_UP;
962 causes control to behave as if up arrow was pressed.
964 dbg('wxTimeCtrl::OnSpinUp', indent
=1)
965 self
.__OnSpin
(WXK_UP
)
966 keep_processing
= False
968 return keep_processing
971 def __OnSpinDown(self
, event
):
973 Event handler for any bound spin button on EVT_SPIN_DOWN;
974 causes control to behave as if down arrow was pressed.
976 dbg('wxTimeCtrl::OnSpinDown', indent
=1)
977 self
.__OnSpin
(WXK_DOWN
)
978 keep_processing
= False
980 return keep_processing
983 def __OnChar(self
, event
):
985 Handler to explicitly look for ':' keyevents, and if found,
986 clear the m_shiftDown field, so it will behave as forward tab.
987 It then calls the base control's _OnChar routine with the modified
990 dbg('wxTimeCtrl::OnChar', indent
=1)
991 keycode
= event
.GetKeyCode()
992 dbg('keycode:', keycode
)
993 if keycode
== ord(':'):
994 dbg('colon seen! removing shift attribute')
995 event
.m_shiftDown
= False
996 wxMaskedTextCtrl
._OnChar
(self
, event
) ## handle each keypress
1000 def __OnSetToNow(self
, event
):
1002 This is the key handler for '!' and 'c'; this allows the user to
1003 quickly set the value of the control to the current time.
1005 self
.SetValue(wx
.DateTime_Now().FormatTime())
1006 keep_processing
= False
1007 return keep_processing
1010 def __LimitSelection(self
, event
):
1012 Event handler for motion events; this handler
1013 changes limits the selection to the new cell boundaries.
1015 dbg('wxTimeCtrl::LimitSelection', indent
=1)
1016 pos
= self
.GetInsertionPoint()
1017 self
.__posCurrent
= pos
1018 sel_start
, sel_to
= self
.GetSelection()
1019 selection
= sel_start
!= sel_to
1021 # only allow selection to end of current cell:
1022 start
, end
= self
._FindField
(sel_start
)._extent
1023 if sel_to
< pos
: sel_to
= start
1024 elif sel_to
> pos
: sel_to
= end
1026 dbg('new pos =', self
.__posCurrent
, 'select to ', sel_to
)
1027 self
.SetInsertionPoint(self
.__posCurrent
)
1028 self
.SetSelection(self
.__posCurrent
, sel_to
)
1029 if event
: event
.Skip()
1033 def __IncrementValue(self
, key
, pos
):
1034 dbg('wxTimeCtrl::IncrementValue', key
, pos
, indent
=1)
1035 text
= self
.GetValue()
1036 field
= self
._FindField
(pos
)
1037 dbg('field: ', field
._index
)
1038 start
, end
= field
._extent
1039 slice = text
[start
:end
]
1040 if key
== wx
.WXK_UP
: increment
= 1
1041 else: increment
= -1
1043 if slice in ('A', 'P'):
1044 if slice == 'A': newslice
= 'P'
1045 elif slice == 'P': newslice
= 'A'
1046 newvalue
= text
[:start
] + newslice
+ text
[end
:]
1048 elif field
._index
== 0:
1049 # adjusting this field is trickier, as its value can affect the
1050 # am/pm setting. So, we use wxDateTime to generate a new value for us:
1051 # (Use a fixed date not subject to DST variations:)
1052 converter
= wx
.DateTimeFromDMY(1, 0, 1970)
1053 dbg('text: "%s"' % text
)
1054 converter
.ParseTime(text
.strip())
1055 currenthour
= converter
.GetHour()
1056 dbg('current hour:', currenthour
)
1057 newhour
= (currenthour
+ increment
) % 24
1058 dbg('newhour:', newhour
)
1059 converter
.SetHour(newhour
)
1060 dbg('converter.GetHour():', converter
.GetHour())
1061 newvalue
= converter
# take advantage of auto-conversion for am/pm in .SetValue()
1063 else: # minute or second field; handled the same way:
1064 newslice
= "%02d" % ((int(slice) + increment
) % 60)
1065 newvalue
= text
[:start
] + newslice
+ text
[end
:]
1068 self
.SetValue(newvalue
)
1070 except ValueError: # must not be in bounds:
1071 if not wx
.Validator_IsSilent():
1076 def _toGUI( self
, wxdt
):
1078 This function takes a wxdt as an unambiguous representation of a time, and
1079 converts it to a string appropriate for the format of the control.
1082 if self
.__display
_seconds
: strval
= wxdt
.Format('%H:%M:%S')
1083 else: strval
= wxdt
.Format('%H:%M')
1085 if self
.__display
_seconds
: strval
= wxdt
.Format('%I:%M:%S %p')
1086 else: strval
= wxdt
.Format('%I:%M %p')
1091 def __validateValue( self
, value
):
1093 This function converts the value to a wxDateTime if not already one,
1094 does bounds checking and raises ValueError if argument is
1095 not a valid value for the control as currently specified.
1096 It is used by both the SetValue() and the IsValid() methods.
1098 dbg('wxTimeCtrl::__validateValue(%s)' % repr(value
), indent
=1)
1101 raise ValueError('%s not a valid time value' % repr(value
))
1103 valid
= True # assume true
1105 value
= self
.GetWxDateTime(value
) # regularize form; can generate ValueError if problem doing so
1107 dbg('exception occurred', indent
=0)
1110 if self
.IsLimited() and not self
.IsInBounds(value
):
1113 'value %s is not within the bounds of the control' % str(value
) )
1117 #----------------------------------------------------------------------------
1118 # Test jig for wxTimeCtrl:
1120 if __name__
== '__main__':
1123 class TestPanel(wx
.Panel
):
1124 def __init__(self
, parent
, id,
1125 pos
= wx
.DefaultPosition
, size
= wx
.DefaultSize
,
1126 fmt24hr
= 0, test_mx
= 0,
1127 style
= wx
.TAB_TRAVERSAL
):
1129 wx
.Panel
.__init
__(self
, parent
, id, pos
, size
, style
)
1131 self
.test_mx
= test_mx
1133 self
.tc
= wxTimeCtrl(self
, 10, fmt24hr
= fmt24hr
)
1134 sb
= wx
.SpinButton( self
, 20, wx
.DefaultPosition
, (-1,20), 0 )
1135 self
.tc
.BindSpinButton(sb
)
1137 sizer
= wx
.BoxSizer( wx
.HORIZONTAL
)
1138 sizer
.Add( self
.tc
, 0, wx
.ALIGN_CENTRE|wx
.LEFT|wx
.TOP|wx
.BOTTOM
, 5 )
1139 sizer
.Add( sb
, 0, wx
.ALIGN_CENTRE|wx
.RIGHT|wx
.TOP|wx
.BOTTOM
, 5 )
1141 self
.SetAutoLayout( True )
1142 self
.SetSizer( sizer
)
1144 sizer
.SetSizeHints( self
)
1146 self
.Bind(EVT_TIMEUPDATE
, self
.OnTimeChange
, self
.tc
)
1148 def OnTimeChange(self
, event
):
1149 dbg('OnTimeChange: value = ', event
.GetValue())
1150 wxdt
= self
.tc
.GetWxDateTime()
1151 dbg('wxdt =', wxdt
.GetHour(), wxdt
.GetMinute(), wxdt
.GetSecond())
1153 mxdt
= self
.tc
.GetMxDateTime()
1154 dbg('mxdt =', mxdt
.hour
, mxdt
.minute
, mxdt
.second
)
1157 class MyApp(wx
.App
):
1160 fmt24hr
= '24' in sys
.argv
1161 test_mx
= 'mx' in sys
.argv
1163 frame
= wx
.Frame(None, -1, "wxTimeCtrl Test", (20,20), (100,100) )
1164 panel
= TestPanel(frame
, -1, (-1,-1), fmt24hr
=fmt24hr
, test_mx
= test_mx
)
1167 traceback
.print_exc()
1175 traceback
.print_exc()