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.
22 from wxPython
.wx
import *
25 # wxWindows' wxTextCtrl translates Composite "control key"
26 # events into single events before returning them to its OnChar
27 # routine. The doc says that this results in 1 for Ctrl-A, 2 for
28 # Ctrl-B, etc. However, there are no wxPython or wxWindows
29 # symbols for them, so I'm defining codes for Ctrl-X (cut) and
30 # Ctrl-V (paste) here for readability:
31 WXK_CTRL_X
= (ord('X')+1) - ord('A')
32 WXK_CTRL_V
= (ord('V')+1) - ord('A')
34 # The following bit of function is for debugging the subsequent code.
35 # To turn on debugging output, set _debug to 1
40 def _dbg(*args
, **kwargs
):
44 if _indent
: print ' ' * 3 * _indent
,
45 for arg
in args
: print arg
,
50 for kwarg
, value
in kwargs
.items():
51 if kwarg
== 'indent' and value
: _indent
= _indent
+ 1
52 elif kwarg
== 'indent' and value
== 0: _indent
= _indent
- 1
53 if _indent
< 0: _indent
= 0
55 def _dbg(*args
, **kwargs
):
59 # This class of event fires whenever the value of the time changes in the control:
60 wxEVT_TIMEVAL_UPDATED
= wxNewId()
61 def EVT_TIMEUPDATE(win
, id, func
):
62 """Used to trap events indicating that the current time has been changed."""
63 win
.Connect(id, -1, wxEVT_TIMEVAL_UPDATED
, func
)
65 class TimeUpdatedEvent(wxPyCommandEvent
):
66 def __init__(self
, id, value
='12:00:00 AM'):
67 wxPyCommandEvent
.__init
__(self
, wxEVT_TIMEVAL_UPDATED
, id)
70 """Retrieve the value of the time control at the time this event was generated"""
74 # Set up all the positions of the cells in the wxTimeCtrl (once at module import):
75 # Format of control is:
78 # positions: 01234567890
79 _listCells
= ['hour', 'minute', 'second', 'am_pm']
80 _listCellRange
= [(0,1,2), (3,4,5), (6,7,8), (9,10,11)]
81 _listDelimPos
= [2,5,8]
83 # Create dictionary of cell ranges, indexed by name or position in the range:
86 _dictCellRange
[_listCells
[i
]] = _listCellRange
[i
]
87 for cell
in _listCells
:
88 for i
in _dictCellRange
[cell
]:
89 _dictCellRange
[i
] = _dictCellRange
[cell
]
92 # Create lists of starting and ending positions for each range, and a dictionary of starting
93 # positions indexed by name
94 _listStartCellPos
= []
96 for tup
in _listCellRange
:
97 _listStartCellPos
.append(tup
[0]) # 1st char of cell
98 _listEndCellPos
.append(tup
[1]) # last char of cell (not including delimiter)
100 _dictStartCellPos
= {}
102 _dictStartCellPos
[_listCells
[i
]] = _listStartCellPos
[i
]
105 class wxTimeCtrl(wxTextCtrl
):
107 self
, parent
, id=-1, value
= '12:00:00 AM',
108 pos
= wxDefaultPosition
, size
= wxDefaultSize
,
111 style
= wxTE_PROCESS_TAB
, name
= "time"
113 wxTextCtrl
.__init
__(self
, parent
, id, value
='',
114 pos
=pos
, size
=size
, style
=style
, name
=name
)
116 self
.__fmt
24hr
= fmt24hr
118 if size
== wxDefaultSize
:
119 # set appropriate default sizes depending on format:
121 testText
= '00:00:00'
123 testText
= '00:00:00 MM'
126 if wxPlatform
!= "__WXMSW__": # give it a little extra space
128 if wxPlatform
== "__WXMAC__": # give it even a little more...
131 w
, h
= self
.GetTextExtent(testText
)
132 self
.SetClientSize( (w
+4, self
.GetClientSize().height
) )
135 if self
.__fmt
24hr
: self
.__lastCell
= 'second'
136 else: self
.__lastCell
= 'am_pm'
138 # Validate initial value and set if appropriate
142 self
.SetValue('12:00:00 AM')
144 # set initial position and selection state
145 self
.__SetCurrentCell
(_dictStartCellPos
['hour'])
146 self
.__OnChangePos
(None)
148 # Set up internal event handlers to change the event reaction behavior of
149 # the base wxTextCtrl:
150 EVT_TEXT(self
, self
.GetId(), self
.__OnTextChange
)
151 EVT_SET_FOCUS(self
, self
.__OnFocus
)
152 EVT_LEFT_UP(self
, self
.__OnChangePos
)
153 EVT_LEFT_DCLICK(self
, self
.__OnDoubleClick
)
154 EVT_CHAR(self
, self
.__OnChar
)
157 self
.BindSpinButton(spinButton
) # bind spin button up/down events to this control
160 def BindSpinButton(self
, sb
):
162 This function binds an externally created spin button to the control, so that
163 up/down events from the button automatically change the control.
165 _dbg('wxTimeCtrl::BindSpinButton')
166 self
.__spinButton
= sb
167 if self
.__spinButton
:
168 # bind event handlers to spin ctrl
169 EVT_SPIN_UP(self
.__spinButton
, self
.__spinButton
.GetId(), self
.__OnSpinUp
)
170 EVT_SPIN_DOWN(self
.__spinButton
, self
.__spinButton
.GetId(), self
.__OnSpinDown
)
175 return "<wxTimeCtrl: %s>" % self
.GetValue()
179 def SetValue(self
, value
):
181 Validating SetValue function for time strings, doing 12/24 format conversion as appropriate.
183 _dbg('wxTimeCtrl::SetValue', indent
=1)
184 dict_range
= _dictCellRange
# (for brevity)
185 dict_start
= _dictStartCellPos
187 fmt12len
= dict_range
['am_pm'][-1]
188 fmt24len
= dict_range
['second'][-1]
190 separators_correct
= value
[2] == ':' and value
[5] == ':'
191 len_ok
= len(value
) in (fmt12len
, fmt24len
)
193 if len(value
) > fmt24len
:
194 separators_correct
= separators_correct
and value
[8] == ' '
195 hour
= int(value
[dict_range
['hour'][0]:dict_range
['hour'][-1]])
196 hour_ok
= ((hour
in range(0,24) and len(value
) == fmt24len
)
197 or (hour
in range(1,13) and len(value
) == fmt12len
198 and value
[dict_start
['am_pm']:] in ('AM', 'PM')))
200 minute
= int(value
[dict_range
['minute'][0]:dict_range
['minute'][-1]])
201 min_ok
= minute
in range(60)
202 second
= int(value
[dict_range
['second'][0]:dict_range
['second'][-1]])
203 sec_ok
= second
in range(60)
205 _dbg('len_ok =', len_ok
, 'separators_correct =', separators_correct
)
206 _dbg('hour =', hour
, 'hour_ok =', hour_ok
, 'min_ok =', min_ok
, 'sec_ok =', sec_ok
)
208 if len_ok
and hour_ok
and min_ok
and sec_ok
and separators_correct
:
209 _dbg('valid time string')
212 if len(value
) == fmt12len
: # handle 12 hour format conversion for actual hour:
213 am
= value
[dict_start
['am_pm']:] == 'AM'
214 if hour
!= 12 and not am
:
215 self
.__hour
= hour
= (hour
+12) % 24
217 if am
: self
.__hour
= hour
= 0
219 self
.__minute
= minute
220 self
.__second
= second
223 need_to_convert
= ((self
.__fmt
24hr
and len(value
) == fmt12len
)
224 or (not self
.__fmt
24hr
and len(value
) == fmt24len
))
225 _dbg('need_to_convert =', need_to_convert
)
227 if need_to_convert
: #convert to 12/24 hour format as specified:
228 if self
.__fmt
24hr
and len(value
) == fmt12len
:
229 text
= '%.2d:%.2d:%.2d' % (hour
, minute
, second
)
237 if hour
== 0: hour
= 12
239 text
= '%2d:%.2d:%.2d %s' % (hour
, minute
, second
, am_pm
)
243 wxTextCtrl
.SetValue(self
, text
)
244 _dbg('firing TimeUpdatedEvent...')
245 evt
= TimeUpdatedEvent(self
.GetId(), text
)
246 evt
.SetEventObject(self
)
247 self
.GetEventHandler().ProcessEvent(evt
)
249 _dbg('len_ok:', len_ok
, 'separators_correct =', separators_correct
)
250 _dbg('hour_ok:', hour_ok
, 'min_ok:', min_ok
, 'sec_ok:', sec_ok
, indent
=0)
251 raise ValueError, 'value is not a valid time string'
253 except (TypeError, ValueError):
255 raise ValueError, 'value is not a valid time string'
258 def SetWxDateTime(self
, wxdt
):
259 value
= '%2d:%.2d:%.2d' % (wxdt
.GetHour(), wxdt
.GetMinute(), wxdt
.GetSecond())
262 def GetWxDateTime(self
):
263 t
= wxDateTimeFromHMS(self
.__hour
, self
.__minute
, self
.__second
)
266 def SetMxDateTime(self
, mxdt
):
267 from mx
import DateTime
268 value
= '%2d:%.2d:%.2d' % (mxdt
.hour
, mxdt
.minute
, mxdt
.second
)
271 def GetMxDateTime(self
):
272 from mx
import DateTime
273 t
= DateTime
.Time(self
.__hour
, self
.__minute
, self
.__second
)
276 #-------------------------------------------------------------------------------------------------------------
277 # these are private functions and overrides:
279 def __SetCurrentCell(self
, pos
):
281 Sets state variables that indicate the current cell and position within the control.
283 self
.__posCurrent
= pos
284 self
.__cellStart
, self
.__cellEnd
= _dictCellRange
[pos
][0], _dictCellRange
[pos
][-1]
287 def SetInsertionPoint(self
, pos
):
289 Records the specified position and associated cell before calling base class' function.
291 _dbg('wxTimeCtrl::SetInsertionPoint', pos
, indent
=1)
293 # Adjust pos to legal value if not already
295 elif pos
in _listDelimPos
+ [_dictCellRange
[self
.__lastCell
]]:
297 if self
.__lastCell
== 'am_pm' and pos
in _dictCellRange
[self
.__lastCell
]:
298 pos
= _dictStartCellPos
[self
.__lastCell
]
300 self
.__SetCurrentCell
(pos
)
301 wxTextCtrl
.SetInsertionPoint(self
, pos
) # (causes EVT_TEXT event to fire)
305 def SetSelection(self
, sel_start
, sel_to
):
306 _dbg('wxTimeCtrl::SetSelection', sel_start
, sel_to
, indent
=1)
308 # Adjust selection range to legal extent if not already
310 self
.SetInsertionPoint(0)
311 sel_start
= self
.__posCurrent
313 elif sel_start
in _listDelimPos
+ [_dictCellRange
[self
.__lastCell
]]:
314 self
.SetInsertionPoint(sel_start
- 1)
315 sel_start
= self
.__posCurrent
317 if self
.__posCurrent
!= sel_start
: # force selection and insertion point to match
318 self
.SetInsertionPoint(sel_start
)
320 if sel_to
not in _dictCellRange
[sel_start
]:
321 sel_to
= _dictCellRange
[sel_start
][-1] # limit selection to end of current cell
323 self
.__bSelection
= sel_start
!= sel_to
324 self
.__posSelectTo
= sel_to
325 wxTextCtrl
.SetSelection(self
, sel_start
, sel_to
)
329 def __OnFocus(self
,event
):
331 This event handler is currently necessary to work around new default
332 behavior as of wxPython2.3.3;
333 The TAB key auto selects the entire contents of the wxTextCtrl *after*
334 the EVT_SET_FOCUS event occurs; therefore we can't query/adjust the selection
335 *here*, because it hasn't happened yet. So to prevent this behavior, and
336 preserve the correct selection when the focus event is not due to tab,
337 we need to pull the following trick:
339 _dbg('wxTimeCtrl::OnFocus')
340 wxCallAfter(self
.__FixSelection
)
344 def __FixSelection(self
):
346 This gets called after the TAB traversal selection is made, if the
347 focus event was due to this, but before the EVT_LEFT_* events if
348 the focus shift was due to a mouse event.
350 The trouble is that, a priori, there's no explicit notification of
351 why the focus event we received. However, the whole reason we need to
352 do this is because the default behavior on TAB traveral in a wxTextCtrl is
353 now to select the entire contents of the window, something we don't want.
354 So we can *now* test the selection range, and if it's "the whole text"
355 we can assume the cause, change the insertion point to the start of
356 the control, and deselect.
358 _dbg('wxTimeCtrl::FixSelection', indent
=1)
359 sel_start
, sel_to
= self
.GetSelection()
360 if sel_start
== 0 and sel_to
in _dictCellRange
[self
.__lastCell
]:
361 # This isn't normally allowed, and so assume we got here by the new
362 # "tab traversal" behavior, so we need to reset the selection
363 # and insertion point:
364 _dbg('entire text selected; resetting selection to start of control')
365 self
.SetInsertionPoint(0)
366 self
.SetSelection(self
.__cellStart
, self
.__cellEnd
)
370 def __OnTextChange(self
, event
):
372 This private event handler is required to retain the current position information of the cursor
373 after update to the underlying text control is done.
375 _dbg('wxTimeCtrl::OnTextChange', indent
=1)
376 self
.__SetCurrentCell
(self
.__posCurrent
) # ensure cell range vars are set
378 # Note: must call self.SetSelection here to preserve insertion point cursor after update!
379 # (I don't know why, but this does the trick!)
380 if self
.__bSelection
:
381 _dbg('reselecting from ', self
.__posCurrent
, 'to', self
.__posSelectTo
)
382 self
.SetSelection(self
.__posCurrent
, self
.__posSelectTo
)
384 self
.SetSelection(self
.__posCurrent
, self
.__posCurrent
)
388 def __OnSpin(self
, key
):
389 self
.__IncrementValue
(key
, self
.__posCurrent
)
391 self
.SetInsertionPoint(self
.__posCurrent
)
392 self
.SetSelection(self
.__posCurrent
, self
.__posSelectTo
)
394 def __OnSpinUp(self
, event
):
396 Event handler for any bound spin button on EVT_SPIN_UP;
397 causes control to behave as if up arrow was pressed.
399 _dbg('wxTimeCtrl::OnSpinUp', indent
=1)
400 self
.__OnSpin
(WXK_UP
)
404 def __OnSpinDown(self
, event
):
406 Event handler for any bound spin button on EVT_SPIN_DOWN;
407 causes control to behave as if down arrow was pressed.
409 _dbg('wxTimeCtrl::OnSpinDown', indent
=1)
410 self
.__OnSpin
(WXK_DOWN
)
414 def __OnChangePos(self
, event
):
416 Event handler for motion events; this handler
417 changes limits the selection to the new cell boundaries.
419 _dbg('wxTimeCtrl::OnChangePos', indent
=1)
420 pos
= self
.GetInsertionPoint()
421 self
.__SetCurrentCell
(pos
)
422 sel_start
, sel_to
= self
.GetSelection()
423 selection
= sel_start
!= sel_to
425 # disallow position at end of field:
426 if pos
in _listDelimPos
+ [_dictCellRange
[self
.__lastCell
][-1]]:
427 self
.SetInsertionPoint(pos
-1)
428 self
.__posSelectTo
= self
.__cellEnd
430 # only allow selection to end of current cell:
431 if sel_to
< pos
: self
.__posSelectTo
= self
.__cellStart
432 elif sel_to
> pos
: self
.__posSelectTo
= self
.__cellEnd
434 _dbg('new pos =', self
.__posCurrent
, 'select to ', self
.__posSelectTo
)
435 self
.SetSelection(self
.__posCurrent
, self
.__posSelectTo
)
436 if event
: event
.Skip()
440 def __OnDoubleClick(self
, event
):
442 Event handler for left double-click mouse events; this handler
443 causes the cell at the double-click point to be selected.
445 _dbg('wxTimeCtrl::OnDoubleClick', indent
=1)
446 pos
= self
.GetInsertionPoint()
447 self
.__SetCurrentCell
(pos
)
448 if self
.__posCurrent
!= self
.__cellStart
:
449 self
.SetInsertionPoint(self
.__cellStart
)
450 self
.SetSelection(self
.__cellStart
, self
.__cellEnd
)
454 def __OnChar(self
, event
):
456 This private event handler is the main control point for the wxTimeCtrl.
457 It governs whether input characters are accepted and if so, handles them
458 so as to provide appropriate cursor and selection behavior for the control.
460 _dbg('wxTimeCtrl::OnChar', indent
=1)
462 # NOTE: Returning without calling event.Skip() eats the event before it
463 # gets to the text control...
465 key
= event
.GetKeyCode()
466 text
= self
.GetValue()
467 pos
= self
.GetInsertionPoint()
468 if pos
!= self
.__posCurrent
:
469 _dbg("insertion point has moved; resetting current cell")
470 self
.__SetCurrentCell
(pos
)
471 self
.SetSelection(self
.__posCurrent
, self
.__posCurrent
)
473 sel_start
, sel_to
= self
.GetSelection()
474 selection
= sel_start
!= sel_to
475 _dbg('sel_start=', sel_start
, 'sel_to =', sel_to
)
477 self
.__bSelection
= False # predict unselection of entire region
479 _dbg('keycode = ', key
)
482 # don't allow deletion, cut or paste:
483 if key
in (WXK_DELETE
, WXK_BACK
, WXK_CTRL_X
, WXK_CTRL_V
):
486 elif key
== WXK_TAB
: # skip to next field if applicable:
487 _dbg('key == WXK_TAB')
488 dict_range
= _dictCellRange
# (for brevity)
489 dict_start
= _dictStartCellPos
490 if event
.ShiftDown(): # tabbing backwords
492 ###(NOTE: doesn't work; wxTE_PROCESS_TAB doesn't appear to send us this event!)
494 _dbg('event.ShiftDown()')
495 if pos
in dict_range
['hour']: # already in 1st field
496 self
.__SetCurrentCell
(dict_start
['hour']) # ensure we have our member vars set
497 event
.Skip() #then do normal tab processing for the form
501 elif pos
in dict_range
['minute']: # skip to hours field
502 new_pos
= dict_start
['hour']
503 elif pos
in dict_range
['second']: # skip to minutes field
504 new_pos
= dict_start
['minute']
505 elif pos
in dict_range
['am_pm']: # skip to seconds field
506 new_pos
= dict_start
['second']
508 self
.SetInsertionPoint(new_pos
) # force insert point to jump to next cell (swallowing TAB)
509 self
.__OnChangePos
(None) # update selection accordingly
512 # Tabbing forwards through control...
514 if pos
in dict_range
[self
.__lastCell
]: # already in last field; ensure we have our members set
515 self
.__SetCurrentCell
(dict_start
[self
.__lastCell
])
516 _dbg('tab in last cell')
517 event
.Skip() # then do normal tab processing for the form
521 if pos
in dict_range
['second']: # skip to AM/PM field (if not last cell)
522 new_pos
= dict_start
['am_pm']
523 elif pos
in dict_range
['minute']: # skip to seconds field
524 new_pos
= dict_start
['second']
525 elif pos
in dict_range
['hour']: # skip to minutes field
526 new_pos
= dict_start
['minute']
528 self
.SetInsertionPoint(new_pos
) # force insert point to jump to next cell (swallowing TAB)
529 self
.__OnChangePos
(None) # update selection accordingly
531 elif key
== WXK_LEFT
: # move left; set insertion point as appropriate:
532 _dbg('key == WXK_LEFT')
533 if event
.ShiftDown(): # selecting a range...
534 _dbg('event.ShiftDown()')
535 if pos
in _listStartCellPos
: # can't select pass delimiters
536 if( sel_to
== pos
+2 and sel_to
!= _dictCellRange
['am_pm'][-1]):
537 self
.SetSelection(pos
, pos
+1) # allow deselection of 2nd char in cell if not am/pm
540 elif pos
in _listEndCellPos
: # can't use normal selection, because position ends up
542 _dbg('set selection from', pos
-1, 'to', self
.__posCurrent
)
543 self
.SetInsertionPoint(pos
-1) # this selects the previous position
544 self
.SetSelection(self
.__posCurrent
, pos
)
546 self
.SetInsertionPoint(sel_to
- 1) # this unselects the last digit
547 self
.SetSelection(self
.__posCurrent
, pos
)
549 else: # ... not selecting
550 if pos
== 0: # can't position before position 0
552 elif pos
in _listStartCellPos
: # skip (left) OVER the colon/space:
553 self
.SetInsertionPoint(pos
-2)
554 self
.__OnChangePos
(None) # set the selection appropriately
556 self
.SetInsertionPoint(pos
-1) # reposition the cursor and
557 self
.__OnChangePos
(None) # set the selection appropriately
560 elif key
== WXK_RIGHT
: # move right
561 _dbg('key == WXK_RIGHT')
562 if event
.ShiftDown():
563 _dbg('event.ShiftDown()')
564 if sel_to
in _listDelimPos
: # can't select pass delimiters
567 self
.SetSelection(self
.__posCurrent
, sel_to
+1)
569 if( (self
.__lastCell
== 'second'
570 and pos
== _dictStartCellPos
['second']+1)
571 or (self
.__lastCell
== 'am_pm'
572 and pos
== _dictStartCellPos
['am_pm']) ):
573 pass # don't allow cursor past last cell
574 elif pos
in _listEndCellPos
: # skip (right) OVER the colon/space:
575 self
.SetInsertionPoint(pos
+2)
576 self
.__OnChangePos
(None) # set the selection appropriately
578 self
.SetInsertionPoint(pos
+1) # reposition the cursor and
579 self
.__OnChangePos
(None) # set the selection appropriately
581 elif key
in (WXK_UP
, WXK_DOWN
):
582 _dbg('key in (WXK_UP, WXK_DOWN)')
583 self
.__IncrementValue
(key
, pos
) # increment/decrement as appropriate
586 elif key
< WXK_SPACE
or key
== WXK_DELETE
or key
> 255:
587 event
.Skip() # non alphanumeric; process normally (Right thing to do?)
589 elif chr(key
) in ['!', 'c', 'C']: # Special character; sets the value of the control to "now"
590 _dbg("key == '!'; setting time to 'now'")
591 now
= wxDateTime_Now()
592 self
.SetWxDateTime(now
)
594 elif chr(key
) in string
.digits
: # let ChangeValue validate and update current position
595 self
.__ChangeValue
(chr(key
), pos
) # handle event (and swallow it)
597 elif chr(key
) in ('a', 'A', 'p', 'P', ' '): # let ChangeValue validate and update current position
598 self
.__ChangeValue
(chr(key
), pos
) # handle event (and swallow it)
600 else: # disallowed char; swallow event
605 def __IncrementValue(self
, key
, pos
):
606 _dbg('wxTimeCtrl::IncrementValue', key
, pos
, indent
=1)
607 text
= self
.GetValue()
609 sel_start
, sel_to
= self
.GetSelection()
610 selection
= sel_start
!= sel_to
611 cell_selected
= selection
and sel_to
-1 != pos
613 dict_start
= _dictStartCellPos
# (for brevity)
615 # Determine whether we should change the entire cell or just a portion of it:
617 or (pos
in _listStartCellPos
and not selection
)
618 or (text
[pos
] == ' ' and text
[pos
+1] not in ('1', '2'))
619 or (text
[pos
] == '9' and text
[pos
-1] == ' ' and key
== WXK_UP
)
620 or (text
[pos
] == '1' and text
[pos
-1] == ' ' and key
== WXK_DOWN
)
621 or pos
>= dict_start
['am_pm']):
623 self
.__IncrementCell
(key
, pos
)
625 if key
== WXK_UP
: inc
= 1
628 if pos
== dict_start
['hour'] and not self
.__fmt
24hr
:
629 if text
[pos
] == ' ': digit
= '1' # allow ' ' or 1 for 1st digit in 12hr format
632 if pos
== dict_start
['hour']:
633 if int(text
[pos
+ 1]) >3: mod
= 2 # allow for 20-23
634 else: mod
= 3 # allow 00-19
635 elif pos
== dict_start
['hour'] + 1:
637 if text
[pos
- 1] == '2': mod
= 4 # allow hours 20-23
638 else: mod
= 10 # allow hours 00-19
640 if text
[pos
- 1] == '1': mod
= 3 # allow hours 10-12
641 else: mod
= 10 # allow 0-9
643 elif pos
in (dict_start
['minute'],
644 dict_start
['second']): mod
= 6 # allow minutes/seconds 00-59
647 digit
= '%d' % ((int(text
[pos
]) + inc
) % mod
)
649 _dbg("new digit = \'%s\'" % digit
)
650 self
.__ChangeValue
(digit
, pos
)
654 def __IncrementCell(self
, key
, pos
):
655 _dbg('wxTimeCtrl::IncrementCell', key
, pos
, indent
=1)
656 self
.__SetCurrentCell
(pos
) # determine current cell
657 hour
, minute
, second
= self
.__hour
, self
.__minute
, self
.__second
658 text
= self
.GetValue()
659 dict_start
= _dictStartCellPos
# (for brevity)
660 if key
== WXK_UP
: inc
= 1
663 if self
.__cellStart
== dict_start
['am_pm']:
664 am
= text
[dict_start
['am_pm']:] == 'AM'
665 if am
: hour
= hour
+ 12
666 else: hour
= hour
- 12
668 if self
.__cellStart
== dict_start
['hour']:
669 hour
= (hour
+ inc
) % 24
670 elif self
.__cellStart
== dict_start
['minute']:
671 minute
= (minute
+ inc
) % 60
672 elif self
.__cellStart
== dict_start
['second']:
673 second
= (second
+ inc
) % 60
675 newvalue
= '%.2d:%.2d:%.2d' % (hour
, minute
, second
)
677 self
.SetValue(newvalue
)
678 self
.SetInsertionPoint(self
.__cellStart
)
679 self
.SetSelection(self
.__cellStart
, self
.__cellEnd
)
683 def __ChangeValue(self
, char
, pos
):
684 _dbg('wxTimeCtrl::ChangeValue', "\'" + char
+ "\'", pos
, indent
=1)
685 text
= self
.GetValue()
687 self
.__SetCurrentCell
(pos
)
688 sel_start
, sel_to
= self
.GetSelection()
689 self
.__posSelectTo
= sel_to
690 self
.__bSelection
= selection
= sel_start
!= sel_to
691 cell_selected
= selection
and sel_to
-1 != pos
692 _dbg('cell_selected =', cell_selected
, indent
=0)
694 dict_start
= _dictStartCellPos
# (for brevity)
696 if pos
in _listDelimPos
: return # don't allow change of punctuation
698 elif( 0 < pos
< dict_start
['am_pm'] and char
not in string
.digits
):
699 return # AM/PM not allowed in this position
701 # See if we're changing the hour cell, and validate/update appropriately:
703 hour_start
= dict_start
['hour'] # (ie. 0)
705 if pos
== hour_start
: # if at 1st position,
706 if self
.__fmt
24hr
: # and using 24 hour format
707 if cell_selected
: # replace cell contents with hour represented by digit
708 newtext
= '%.2d' % int(char
) + text
[hour_start
+2:]
709 elif char
not in ('0', '1', '2'): # return if digit not 0,1, or 2
711 else: # relace current position
712 newtext
= char
+ text
[pos
+1:]
713 else: # (12 hour format)
715 if char
== ' ': return # can't erase entire cell
716 elif char
== '0': # treat 0 as '12'
717 newtext
= '12' + text
[hour_start
+2:]
718 else: # replace cell contents with hour represented by digit
719 newtext
= '%2d' % int(char
) + text
[hour_start
+2:]
721 if char
not in ('1', ' '): # can only type a 1 or space
723 if text
[pos
+1] not in ('0', '1', '2'): # and then, only if other column is 0,1, or 2
725 if char
== ' ' and text
[pos
+1] == '0': # and char isn't space if 2nd column is 0
727 else: # ok; replace current position
728 newtext
= char
+ text
[pos
+1:]
729 if char
== ' ': self
.SetInsertionPoint(pos
+1) # move insert point to legal position
731 elif pos
== hour_start
+1: # if editing 2nd position of hour
732 if( not self
.__fmt
24hr
# and using 12 hour format
733 and text
[hour_start
] == '1' # if 1st char is 1,
734 and char
not in ('0', '1', '2')): # disallow anything bug 0,1, or 2
736 newtext
= text
[hour_start
] + char
+ text
[hour_start
+2:] # else any digit ok
738 # Do the same sort of validation for minute and second cells
739 elif pos
in (dict_start
['minute'], dict_start
['second']):
740 if cell_selected
: # if cell selected, replace value
741 newtext
= text
[:pos
] + '%.2d' % int(char
) + text
[pos
+2:]
742 elif int(char
) > 5: return # else disallow > 59 for minute and second fields
744 newtext
= text
[:pos
] + char
+ text
[pos
+1:] # else ok
746 elif pos
in (dict_start
['minute']+1, dict_start
['second']+1):
747 newtext
= text
[:pos
] + char
+ text
[pos
+1:] # all digits ok for 2nd digit of minute/second
750 elif pos
== dict_start
['am_pm']:
752 if char
not in ('A','P'): return # disallow all but A or P as 1st char of column
753 newtext
= text
[:pos
] + char
+ text
[pos
+1:]
754 else: return # not a valid position
757 # update member position vars and set selection to character changed
758 if not cell_selected
:
759 _dbg('reselecting current digit')
760 self
.__posSelectTo
= pos
+1
762 _dbg('newtext=', newtext
)
763 self
.SetValue(newtext
)
764 self
.SetInsertionPoint(self
.__posCurrent
)
765 self
.SetSelection(self
.__posCurrent
, self
.__posSelectTo
)
771 Override wxTextCtrl::Cut() method, as this operation should not
772 be allowed for wxTimeCtrls.
779 Override wxTextCtrl::Paste() method, as this operation should not
780 be allowed for wxTimeCtrls.
785 #----------------------------------------------------------------------------
786 # Test jig for wxTimeCtrl:
788 if __name__
== '__main__':
791 class TestPanel(wxPanel
):
792 def __init__(self
, parent
, id,
793 pos
= wxPyDefaultPosition
, size
= wxPyDefaultSize
,
794 fmt24hr
= 0, test_mx
= 0,
795 style
= wxTAB_TRAVERSAL
):
797 wxPanel
.__init
__(self
, parent
, id, pos
, size
, style
)
799 self
.test_mx
= test_mx
801 self
.tc
= wxTimeCtrl(self
, 10, fmt24hr
= fmt24hr
)
802 sb
= wxSpinButton( self
, 20, wxDefaultPosition
, wxSize(-1,20), 0 )
803 self
.tc
.BindSpinButton(sb
)
805 sizer
= wxBoxSizer( wxHORIZONTAL
)
806 sizer
.AddWindow( self
.tc
, 0, wxALIGN_CENTRE|wxLEFT|wxTOP|wxBOTTOM
, 5 )
807 sizer
.AddWindow( sb
, 0, wxALIGN_CENTRE|wxRIGHT|wxTOP|wxBOTTOM
, 5 )
809 self
.SetAutoLayout( True )
810 self
.SetSizer( sizer
)
812 sizer
.SetSizeHints( self
)
814 EVT_TIMEUPDATE(self
, self
.tc
.GetId(), self
.OnTimeChange
)
816 def OnTimeChange(self
, event
):
817 _dbg('OnTimeChange: value = ', event
.GetValue())
818 wxdt
= self
.tc
.GetWxDateTime()
819 _dbg('wxdt =', wxdt
.GetHour(), wxdt
.GetMinute(), wxdt
.GetSecond())
821 mxdt
= self
.tc
.GetMxDateTime()
822 _dbg('mxdt =', mxdt
.hour
, mxdt
.minute
, mxdt
.second
)
828 fmt24hr
= '24' in sys
.argv
829 test_mx
= 'mx' in sys
.argv
831 frame
= wxFrame(NULL
, -1, "wxTimeCtrl Test", wxPoint(20,20), wxSize(100,100) )
832 panel
= TestPanel(frame
, -1, wxPoint(-1,-1), fmt24hr
=fmt24hr
, test_mx
= test_mx
)
835 traceback
.print_exc()
843 traceback
.print_exc()