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