]> git.saurik.com Git - wxWidgets.git/blob - wxPython/wxPython/lib/timectrl.py
52d9c4b4fc00566c2b2226369d9e05ff18d1cd86
[wxWidgets.git] / wxPython / wxPython / lib / timectrl.py
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
22 from wxPython.wx import *
23 import string
24
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')
33
34 # The following bit of function is for debugging the subsequent code.
35 # To turn on debugging output, set _debug to 1
36 _debug = 0
37 _indent = 0
38
39 if _debug:
40 def _dbg(*args, **kwargs):
41 global _indent
42
43 if len(args):
44 if _indent: print ' ' * 3 * _indent,
45 for arg in args: print arg,
46 print
47 # else do nothing
48
49 # post process args:
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
54 else:
55 def _dbg(*args, **kwargs):
56 pass
57
58
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)
64
65 class TimeUpdatedEvent(wxPyCommandEvent):
66 def __init__(self, id, value ='12:00:00 AM'):
67 wxPyCommandEvent.__init__(self, wxEVT_TIMEVAL_UPDATED, id)
68 self.value = value
69 def GetValue(self):
70 """Retrieve the value of the time control at the time this event was generated"""
71 return self.value
72
73
74 # Set up all the positions of the cells in the wxTimeCtrl (once at module import):
75 # Format of control is:
76 # hh:mm:ss xM
77 # 1
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]
82
83 # Create dictionary of cell ranges, indexed by name or position in the range:
84 _dictCellRange = {}
85 for i in range(4):
86 _dictCellRange[_listCells[i]] = _listCellRange[i]
87 for cell in _listCells:
88 for i in _dictCellRange[cell]:
89 _dictCellRange[i] = _dictCellRange[cell]
90
91
92 # Create lists of starting and ending positions for each range, and a dictionary of starting
93 # positions indexed by name
94 _listStartCellPos = []
95 _listEndCellPos = []
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)
99
100 _dictStartCellPos = {}
101 for i in range(4):
102 _dictStartCellPos[_listCells[i]] = _listStartCellPos[i]
103
104
105 class wxTimeCtrl(wxTextCtrl):
106 def __init__ (
107 self, parent, id=-1, value = '12:00:00 AM',
108 pos = wxDefaultPosition, size = wxDefaultSize,
109 fmt24hr=0,
110 spinButton = None,
111 style = wxTE_PROCESS_TAB, name = "time"
112 ):
113 wxTextCtrl.__init__(self, parent, id, value='',
114 pos=pos, size=size, style=style, name=name)
115
116 self.__fmt24hr = fmt24hr
117
118 if size == wxDefaultSize:
119 # set appropriate default sizes depending on format:
120 if self.__fmt24hr:
121 testText = '00:00:00'
122 else:
123 testText = '00:00:00 MM'
124 _dbg(wxPlatform)
125
126 if wxPlatform != "__WXMSW__": # give it a little extra space
127 testText += 'M'
128 if wxPlatform == "__WXMAC__": # give it even a little more...
129 testText += 'M'
130
131 w, h = self.GetTextExtent(testText)
132 self.SetClientSize( (w+4, self.GetClientSize().height) )
133
134
135 if self.__fmt24hr: self.__lastCell = 'second'
136 else: self.__lastCell = 'am_pm'
137
138 # Validate initial value and set if appropriate
139 try:
140 self.SetValue(value)
141 except:
142 self.SetValue('12:00:00 AM')
143
144 # set initial position and selection state
145 self.__SetCurrentCell(_dictStartCellPos['hour'])
146 self.__OnChangePos(None)
147
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)
155
156 if spinButton:
157 self.BindSpinButton(spinButton) # bind spin button up/down events to this control
158
159
160 def BindSpinButton(self, sb):
161 """
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.
164 """
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)
171
172
173
174 def __repr__(self):
175 return "<wxTimeCtrl: %s>" % self.GetValue()
176
177
178
179 def SetValue(self, value):
180 """
181 Validating SetValue function for time strings, doing 12/24 format conversion as appropriate.
182 """
183 _dbg('wxTimeCtrl::SetValue', indent=1)
184 dict_range = _dictCellRange # (for brevity)
185 dict_start = _dictStartCellPos
186
187 fmt12len = dict_range['am_pm'][-1]
188 fmt24len = dict_range['second'][-1]
189 try:
190 separators_correct = value[2] == ':' and value[5] == ':'
191 len_ok = len(value) in (fmt12len, fmt24len)
192
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')))
199
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)
204
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)
207
208 if len_ok and hour_ok and min_ok and sec_ok and separators_correct:
209 _dbg('valid time string')
210
211 self.__hour = hour
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
216 elif hour == 12:
217 if am: self.__hour = hour = 0
218
219 self.__minute = minute
220 self.__second = second
221
222 # valid time
223 need_to_convert = ((self.__fmt24hr and len(value) == fmt12len)
224 or (not self.__fmt24hr and len(value) == fmt24len))
225 _dbg('need_to_convert =', need_to_convert)
226
227 if need_to_convert: #convert to 12/24 hour format as specified:
228 if self.__fmt24hr and len(value) == fmt12len:
229 text = '%.2d:%.2d:%.2d' % (hour, minute, second)
230 else:
231 if hour > 12:
232 hour = hour - 12
233 am_pm = 'PM'
234 elif hour == 12:
235 am_pm = 'PM'
236 else:
237 if hour == 0: hour = 12
238 am_pm = 'AM'
239 text = '%2d:%.2d:%.2d %s' % (hour, minute, second, am_pm)
240 else:
241 text = value
242 _dbg('text=', text)
243 wxTextCtrl.SetValue(self, text)
244 _dbg('firing TimeUpdatedEvent...')
245 evt = TimeUpdatedEvent(self.GetId(), text)
246 evt.SetEventObject(self)
247 self.GetEventHandler().ProcessEvent(evt)
248 else:
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'
252
253 except (TypeError, ValueError):
254 _dbg(indent=0)
255 raise ValueError, 'value is not a valid time string'
256 _dbg(indent=0)
257
258 def SetWxDateTime(self, wxdt):
259 value = '%2d:%.2d:%.2d' % (wxdt.GetHour(), wxdt.GetMinute(), wxdt.GetSecond())
260 self.SetValue(value)
261
262 def GetWxDateTime(self):
263 t = wxDateTimeFromHMS(self.__hour, self.__minute, self.__second)
264 return t
265
266 def SetMxDateTime(self, mxdt):
267 from mx import DateTime
268 value = '%2d:%.2d:%.2d' % (mxdt.hour, mxdt.minute, mxdt.second)
269 self.SetValue(value)
270
271 def GetMxDateTime(self):
272 from mx import DateTime
273 t = DateTime.Time(self.__hour, self.__minute, self.__second)
274 return t
275
276 #-------------------------------------------------------------------------------------------------------------
277 # these are private functions and overrides:
278
279 def __SetCurrentCell(self, pos):
280 """
281 Sets state variables that indicate the current cell and position within the control.
282 """
283 self.__posCurrent = pos
284 self.__cellStart, self.__cellEnd = _dictCellRange[pos][0], _dictCellRange[pos][-1]
285
286
287 def SetInsertionPoint(self, pos):
288 """
289 Records the specified position and associated cell before calling base class' function.
290 """
291 _dbg('wxTimeCtrl::SetInsertionPoint', pos, indent=1)
292
293 # Adjust pos to legal value if not already
294 if pos < 0: pos = 0
295 elif pos in _listDelimPos + [_dictCellRange[self.__lastCell]]:
296 pos = pos - 1
297 if self.__lastCell == 'am_pm' and pos in _dictCellRange[self.__lastCell]:
298 pos = _dictStartCellPos[self.__lastCell]
299
300 self.__SetCurrentCell(pos)
301 wxTextCtrl.SetInsertionPoint(self, pos) # (causes EVT_TEXT event to fire)
302 _dbg(indent=0)
303
304
305 def SetSelection(self, sel_start, sel_to):
306 _dbg('wxTimeCtrl::SetSelection', sel_start, sel_to, indent=1)
307
308 # Adjust selection range to legal extent if not already
309 if sel_start < 0:
310 self.SetInsertionPoint(0)
311 sel_start = self.__posCurrent
312
313 elif sel_start in _listDelimPos + [_dictCellRange[self.__lastCell]]:
314 self.SetInsertionPoint(sel_start - 1)
315 sel_start = self.__posCurrent
316
317 if self.__posCurrent != sel_start: # force selection and insertion point to match
318 self.SetInsertionPoint(sel_start)
319
320 if sel_to not in _dictCellRange[sel_start]:
321 sel_to = _dictCellRange[sel_start][-1] # limit selection to end of current cell
322
323 self.__bSelection = sel_start != sel_to
324 self.__posSelectTo = sel_to
325 wxTextCtrl.SetSelection(self, sel_start, sel_to)
326 _dbg(indent=0)
327
328
329 def __OnFocus(self,event):
330 """
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:
338 """
339 _dbg('wxTimeCtrl::OnFocus')
340 wxCallAfter(self.__FixSelection)
341 event.Skip()
342
343
344 def __FixSelection(self):
345 """
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.
349
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.
357 """
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)
367 _dbg(indent=0)
368
369
370 def __OnTextChange(self, event):
371 """
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.
374 """
375 _dbg('wxTimeCtrl::OnTextChange', indent=1)
376 self.__SetCurrentCell(self.__posCurrent) # ensure cell range vars are set
377
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)
383 else:
384 self.SetSelection(self.__posCurrent, self.__posCurrent)
385 event.Skip()
386 _dbg(indent=0)
387
388 def __OnSpin(self, key):
389 self.__IncrementValue(key, self.__posCurrent)
390 self.SetFocus()
391 self.SetInsertionPoint(self.__posCurrent)
392 self.SetSelection(self.__posCurrent, self.__posSelectTo)
393
394 def __OnSpinUp(self, event):
395 """
396 Event handler for any bound spin button on EVT_SPIN_UP;
397 causes control to behave as if up arrow was pressed.
398 """
399 _dbg('wxTimeCtrl::OnSpinUp', indent=1)
400 self.__OnSpin(WXK_UP)
401 _dbg(indent=0)
402
403
404 def __OnSpinDown(self, event):
405 """
406 Event handler for any bound spin button on EVT_SPIN_DOWN;
407 causes control to behave as if down arrow was pressed.
408 """
409 _dbg('wxTimeCtrl::OnSpinDown', indent=1)
410 self.__OnSpin(WXK_DOWN)
411 _dbg(indent=0)
412
413
414 def __OnChangePos(self, event):
415 """
416 Event handler for motion events; this handler
417 changes limits the selection to the new cell boundaries.
418 """
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
424 if not selection:
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
429 else:
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
433
434 _dbg('new pos =', self.__posCurrent, 'select to ', self.__posSelectTo)
435 self.SetSelection(self.__posCurrent, self.__posSelectTo)
436 if event: event.Skip()
437 _dbg(indent=0)
438
439
440 def __OnDoubleClick(self, event):
441 """
442 Event handler for left double-click mouse events; this handler
443 causes the cell at the double-click point to be selected.
444 """
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)
451 _dbg(indent=0)
452
453
454 def __OnChar(self, event):
455 """
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.
459 """
460 _dbg('wxTimeCtrl::OnChar', indent=1)
461
462 # NOTE: Returning without calling event.Skip() eats the event before it
463 # gets to the text control...
464
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)
472
473 sel_start, sel_to = self.GetSelection()
474 selection = sel_start != sel_to
475 _dbg('sel_start=', sel_start, 'sel_to =', sel_to)
476 if not selection:
477 self.__bSelection = False # predict unselection of entire region
478
479 _dbg('keycode = ', key)
480 _dbg('pos = ', pos)
481
482 # don't allow deletion, cut or paste:
483 if key in (WXK_DELETE, WXK_BACK, WXK_CTRL_X, WXK_CTRL_V):
484 pass
485
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
491
492 ###(NOTE: doesn't work; wxTE_PROCESS_TAB doesn't appear to send us this event!)
493
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
498 _dbg(indent=0)
499 return
500
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']
507
508 self.SetInsertionPoint(new_pos) # force insert point to jump to next cell (swallowing TAB)
509 self.__OnChangePos(None) # update selection accordingly
510
511 else:
512 # Tabbing forwards through control...
513
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
518 _dbg(indent=0)
519 return
520
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']
527
528 self.SetInsertionPoint(new_pos) # force insert point to jump to next cell (swallowing TAB)
529 self.__OnChangePos(None) # update selection accordingly
530
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
538 # else ignore event
539
540 elif pos in _listEndCellPos: # can't use normal selection, because position ends up
541 # at delimeter
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)
545 else:
546 self.SetInsertionPoint(sel_to - 1) # this unselects the last digit
547 self.SetSelection(self.__posCurrent, pos)
548
549 else: # ... not selecting
550 if pos == 0: # can't position before position 0
551 pass
552 elif pos in _listStartCellPos: # skip (left) OVER the colon/space:
553 self.SetInsertionPoint(pos-2)
554 self.__OnChangePos(None) # set the selection appropriately
555 else:
556 self.SetInsertionPoint(pos-1) # reposition the cursor and
557 self.__OnChangePos(None) # set the selection appropriately
558
559
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
565 pass
566 else:
567 self.SetSelection(self.__posCurrent, sel_to+1)
568 else:
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
577 else:
578 self.SetInsertionPoint(pos+1) # reposition the cursor and
579 self.__OnChangePos(None) # set the selection appropriately
580
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
584
585
586 elif key < WXK_SPACE or key == WXK_DELETE or key > 255:
587 event.Skip() # non alphanumeric; process normally (Right thing to do?)
588
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)
593
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)
596
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)
599
600 else: # disallowed char; swallow event
601 pass
602 _dbg(indent=0)
603
604
605 def __IncrementValue(self, key, pos):
606 _dbg('wxTimeCtrl::IncrementValue', key, pos, indent=1)
607 text = self.GetValue()
608
609 sel_start, sel_to = self.GetSelection()
610 selection = sel_start != sel_to
611 cell_selected = selection and sel_to -1 != pos
612
613 dict_start = _dictStartCellPos # (for brevity)
614
615 # Determine whether we should change the entire cell or just a portion of it:
616 if( cell_selected
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']):
622
623 self.__IncrementCell(key, pos)
624 else:
625 if key == WXK_UP: inc = 1
626 else: inc = -1
627
628 if pos == dict_start['hour'] and not self.__fmt24hr:
629 if text[pos] == ' ': digit = '1' # allow ' ' or 1 for 1st digit in 12hr format
630 else: digit = ' '
631 else:
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:
636 if self.__fmt24hr:
637 if text[pos - 1] == '2': mod = 4 # allow hours 20-23
638 else: mod = 10 # allow hours 00-19
639 else:
640 if text[pos - 1] == '1': mod = 3 # allow hours 10-12
641 else: mod = 10 # allow 0-9
642
643 elif pos in (dict_start['minute'],
644 dict_start['second']): mod = 6 # allow minutes/seconds 00-59
645 else: mod = 10
646
647 digit = '%d' % ((int(text[pos]) + inc) % mod)
648
649 _dbg("new digit = \'%s\'" % digit)
650 self.__ChangeValue(digit, pos)
651 _dbg(indent=0)
652
653
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
661 else: inc = -1
662
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
667 else:
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
674
675 newvalue = '%.2d:%.2d:%.2d' % (hour, minute, second)
676
677 self.SetValue(newvalue)
678 self.SetInsertionPoint(self.__cellStart)
679 self.SetSelection(self.__cellStart, self.__cellEnd)
680 _dbg(indent=0)
681
682
683 def __ChangeValue(self, char, pos):
684 _dbg('wxTimeCtrl::ChangeValue', "\'" + char + "\'", pos, indent=1)
685 text = self.GetValue()
686
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)
693
694 dict_start = _dictStartCellPos # (for brevity)
695
696 if pos in _listDelimPos: return # don't allow change of punctuation
697
698 elif( 0 < pos < dict_start['am_pm'] and char not in string.digits):
699 return # AM/PM not allowed in this position
700
701 # See if we're changing the hour cell, and validate/update appropriately:
702 #
703 hour_start = dict_start['hour'] # (ie. 0)
704
705 if pos == hour_start: # if at 1st position,
706 if self.__fmt24hr: # 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
710 return
711 else: # relace current position
712 newtext = char + text[pos+1:]
713 else: # (12 hour format)
714 if cell_selected:
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:]
720 else:
721 if char not in ('1', ' '): # can only type a 1 or space
722 return
723 if text[pos+1] not in ('0', '1', '2'): # and then, only if other column is 0,1, or 2
724 return
725 if char == ' ' and text[pos+1] == '0': # and char isn't space if 2nd column is 0
726 return
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
730
731 elif pos == hour_start+1: # if editing 2nd position of hour
732 if( not self.__fmt24hr # 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
735 return
736 newtext = text[hour_start] + char + text[hour_start+2:] # else any digit ok
737
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
743 else:
744 newtext = text[:pos] + char + text[pos+1:] # else ok
745
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
748
749 # Process AM/PM cell
750 elif pos == dict_start['am_pm']:
751 char = char.upper()
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
755
756 _dbg(indent=1)
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
761
762 _dbg('newtext=', newtext)
763 self.SetValue(newtext)
764 self.SetInsertionPoint(self.__posCurrent)
765 self.SetSelection(self.__posCurrent, self.__posSelectTo)
766 _dbg(indent=0)
767
768
769 def Cut(self):
770 """
771 Override wxTextCtrl::Cut() method, as this operation should not
772 be allowed for wxTimeCtrls.
773 """
774 return
775
776
777 def Paste(self):
778 """
779 Override wxTextCtrl::Paste() method, as this operation should not
780 be allowed for wxTimeCtrls.
781 """
782 return
783
784
785 #----------------------------------------------------------------------------
786 # Test jig for wxTimeCtrl:
787
788 if __name__ == '__main__':
789 import traceback
790
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 ):
796
797 wxPanel.__init__(self, parent, id, pos, size, style)
798
799 self.test_mx = test_mx
800
801 self.tc = wxTimeCtrl(self, 10, fmt24hr = fmt24hr)
802 sb = wxSpinButton( self, 20, wxDefaultPosition, wxSize(-1,20), 0 )
803 self.tc.BindSpinButton(sb)
804
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 )
808
809 self.SetAutoLayout( True )
810 self.SetSizer( sizer )
811 sizer.Fit( self )
812 sizer.SetSizeHints( self )
813
814 EVT_TIMEUPDATE(self, self.tc.GetId(), self.OnTimeChange)
815
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())
820 if self.test_mx:
821 mxdt = self.tc.GetMxDateTime()
822 _dbg('mxdt =', mxdt.hour, mxdt.minute, mxdt.second)
823
824
825 class MyApp(wxApp):
826 def OnInit(self):
827 import sys
828 fmt24hr = '24' in sys.argv
829 test_mx = 'mx' in sys.argv
830 try:
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)
833 frame.Show(True)
834 except:
835 traceback.print_exc()
836 return False
837 return True
838
839 try:
840 app = MyApp(0)
841 app.MainLoop()
842 except:
843 traceback.print_exc()