diff --git a/wxPython/demo/wxTimeCtrl.py b/wxPython/demo/wxTimeCtrl.py index b824ff0586..c20117afe3 100644 --- a/wxPython/demo/wxTimeCtrl.py +++ b/wxPython/demo/wxTimeCtrl.py @@ -1,78 +1,139 @@ from wxPython.wx import * from wxPython.lib.timectrl import * -import string - #---------------------------------------------------------------------- -class TestPanel(wxPanel): - def __init__(self, parent, log): - wxPanel.__init__(self, parent, -1) +class TestPanel( wxPanel ): + def __init__( self, parent, log ): + + wxPanel.__init__( self, parent, -1 ) self.log = log - panel = wxPanel(self, -1) + panel = wxPanel( self, -1 ) + grid = wxFlexGridSizer( 0, 2, 20, 0 ) - text1 = wxStaticText( panel, 10, "A 12-hour format wxTimeCtrl:", wxDefaultPosition, wxDefaultSize, 0 ) - grid.AddWindow( text1, 0, wxALIGN_RIGHT|wxALIGN_CENTER_VERTICAL, 5 ) - - hsizer1 = wxBoxSizer( wxHORIZONTAL ) - self.time12 = wxTimeCtrl(panel, 20, name="12 hour time") - hsizer1.AddWindow( self.time12, 0, wxALIGN_CENTRE, 5 ) + text1 = wxStaticText( panel, 10, "A 12-hour format wxTimeCtrl:") + self.time12 = wxTimeCtrl( panel, 20, name="12 hour control" ) spin1 = wxSpinButton( panel, 30, wxDefaultPosition, wxSize(-1,20), 0 ) - self.time12.BindSpinButton(spin1) - hsizer1.AddWindow( spin1, 0, wxALIGN_CENTRE, 5 ) - grid.AddSizer( hsizer1, 0, wxALIGN_CENTER_VERTICAL|wxLEFT, 5 ) + self.time12.BindSpinButton( spin1 ) - text2 = wxStaticText( panel, 40, "A 24-hour format wxTimeCtrl:", wxDefaultPosition, wxDefaultSize, 0 ) - grid.AddWindow( text2, 0, wxALIGN_RIGHT|wxALIGN_CENTER_VERTICAL|wxLEFT|wxTOP|wxBOTTOM, 5 ) + grid.AddWindow( text1, 0, wxALIGN_RIGHT, 5 ) + hbox1 = wxBoxSizer( wxHORIZONTAL ) + hbox1.AddWindow( self.time12, 0, wxALIGN_CENTRE, 5 ) + hbox1.AddWindow( spin1, 0, wxALIGN_CENTRE, 5 ) + grid.AddSizer( hbox1, 0, wxLEFT, 5 ) - hsizer2 = wxBoxSizer( wxHORIZONTAL ) - self.time24 = wxTimeCtrl(panel, 50, fmt24hr=true, name="24 hour time") - hsizer2.AddWindow( self.time24, 0, wxALIGN_CENTRE, 5 ) + + text2 = wxStaticText( panel, 40, "A 24-hour format wxTimeCtrl:") + self.time24 = wxTimeCtrl( panel, 50, fmt24hr=true, name="24 hour control" ) spin2 = wxSpinButton( panel, 60, wxDefaultPosition, wxSize(-1,20), 0 ) - self.time24.BindSpinButton(spin2) - hsizer2.AddWindow( spin2, 0, wxALIGN_CENTRE, 5 ) - grid.AddSizer( hsizer2, 0, wxALIGN_CENTER_VERTICAL|wxLEFT, 5 ) + self.time24.BindSpinButton( spin2 ) - panel.SetAutoLayout(true) - panel.SetSizer(grid) - grid.Fit(panel) - panel.Move((50,50)) + grid.AddWindow( text2, 0, wxALIGN_RIGHT|wxTOP|wxBOTTOM, 5 ) + hbox2 = wxBoxSizer( wxHORIZONTAL ) + hbox2.AddWindow( self.time24, 0, wxALIGN_CENTRE, 5 ) + hbox2.AddWindow( spin2, 0, wxALIGN_CENTRE, 5 ) + grid.AddSizer( hbox2, 0, wxLEFT, 5 ) + + + text3 = wxStaticText( panel, 70, "A wxTimeCtrl without a spin button:") + self.spinless_ctrl = wxTimeCtrl( panel, 80, name="spinless control" ) + + grid.AddWindow( text3, 0, wxALIGN_RIGHT|wxTOP|wxBOTTOM, 5 ) + grid.AddWindow( self.spinless_ctrl, 0, wxLEFT, 5 ) + + + buttonChange = wxButton( panel, 100, "Change Controls") + self.radio12to24 = wxRadioButton( panel, 110, "Copy 12-hour time to 24-hour control", wxDefaultPosition, wxDefaultSize, wxRB_GROUP ) + self.radio24to12 = wxRadioButton( panel, 120, "Copy 24-hour time to 12-hour control") + self.radioWx = wxRadioButton( panel, 130, "Set controls to 'now' using wxDateTime") + self.radioMx = wxRadioButton( panel, 140, "Set controls to 'now' using mxDateTime") + + radio_vbox = wxBoxSizer( wxVERTICAL ) + radio_vbox.AddWindow( self.radio12to24, 0, wxALIGN_CENTER_VERTICAL|wxALL, 5 ) + radio_vbox.AddWindow( self.radio24to12, 0, wxALIGN_CENTER_VERTICAL|wxALL, 5 ) + radio_vbox.AddWindow( self.radioWx, 0, wxALIGN_CENTER_VERTICAL|wxALL, 5 ) + radio_vbox.AddWindow( self.radioMx, 0, wxALIGN_CENTER_VERTICAL|wxALL, 5 ) + + box_label = wxStaticBox( panel, 90, "Change Controls through API" ) + buttonbox = wxStaticBoxSizer( box_label, wxHORIZONTAL ) + buttonbox.AddWindow( buttonChange, 0, wxALIGN_CENTRE|wxALL, 5 ) + buttonbox.AddSizer( radio_vbox, 0, wxALIGN_CENTRE|wxALL, 5 ) + + outer_box = wxBoxSizer( wxVERTICAL ) + outer_box.AddSizer( grid, 0, wxALIGN_CENTRE|wxBOTTOM, 20 ) + outer_box.AddSizer( buttonbox, 0, wxALIGN_CENTRE|wxALL, 5 ) + + + # Turn on mxDateTime option only if we can import the module: + try: + from mx import DateTime + except ImportError: + self.radioMx.Enable( false ) + + + panel.SetAutoLayout( true ) + panel.SetSizer( outer_box ) + outer_box.Fit( panel ) + panel.Move( (50,50) ) self.panel = panel - EVT_TIMEUPDATE(self, self.time12.GetId(), self.OnTimeChange) - EVT_TIMEUPDATE(self, self.time24.GetId(), self.OnTimeChange) + + EVT_TIMEUPDATE( self, self.time12.GetId(), self.OnTimeChange ) + EVT_TIMEUPDATE( self, self.time24.GetId(), self.OnTimeChange ) + EVT_TIMEUPDATE( self, self.spinless_ctrl.GetId(), self.OnTimeChange ) + + EVT_BUTTON( self, buttonChange.GetId(), self.OnButtonClick ) - def OnTimeChange(self, event): - timectrl = self.panel.FindWindowById(event.GetId()) - self.log.write('%s = %s\n' % ( - timectrl.GetName(), timectrl.GetValue() ) ) + def OnTimeChange( self, event ): + timectrl = self.panel.FindWindowById( event.GetId() ) + self.log.write('%s time = %s\n' % ( timectrl.GetName(), timectrl.GetValue() ) ) + def OnButtonClick( self, event ): + if self.radio12to24.GetValue(): + self.time24.SetValue( self.time12.GetValue() ) + + elif self.radio24to12.GetValue(): + self.time12.SetValue( self.time24.GetValue() ) + + elif self.radioWx.GetValue(): + now = wxDateTime_Now() + self.time12.SetWxDateTime( now ) + self.time24.SetWxDateTime( now ) + self.spinless_ctrl.SetWxDateTime( now ) + + elif self.radioMx.GetValue(): + from mx import DateTime + now = DateTime.now() + self.time12.SetMxDateTime( now ) + self.time24.SetMxDateTime( now ) + self.spinless_ctrl.SetMxDateTime( now ) #---------------------------------------------------------------------- -def runTest(frame, nb, log): - win = TestPanel(nb, log) +def runTest( frame, nb, log ): + win = TestPanel( nb, log ) return win #---------------------------------------------------------------------- -# It's nice to be able to use HTML here, but line breaks in the triple quoted string -# cause the resulting output to look funny, as they seem to be interpreted by the -# parser... - overview = """

-wxTimeCtrl provides a multi-cell control that allows manipulation of a time value. It supports 12 or 24 hour format, and you can use wxDateTime or mxDateTimet o get/set values from the control. +wxTimeCtrl provides a multi-cell control that allows manipulation of a time +value. It supports 12 or 24 hour format, and you can use wxDateTime or mxDateTime +to get/set values from the control.

-Left/right/tab keys to switch cells within a wxTimeCtrl, and the up/down arrows act like a spin control. wxTimeCtrl also allows for an actual spin button to be attached to the control, so that it acts like the up/down arrow keys. +Left/right/tab keys to switch cells within a wxTimeCtrl, and the up/down arrows act +like a spin control. wxTimeCtrl also allows for an actual spin button to be attached +to the control, so that it acts like the up/down arrow keys.

-Here's the interface for wxTimeCtrl: -

- -
-wxTimeCtrl(parent, id = -1,
+The ! key sets the value of the control to now!
+

+Here's the API for wxTimeCtrl: +

+    wxTimeCtrl(
+         parent, id = -1,
          value = '12:00:00 AM',
          pos = wxDefaultPosition,
          size = wxDefaultSize,
@@ -82,36 +143,71 @@ Here's the interface for wxTimeCtrl:
          name = "time")
 
    -
    value
    If no initial value is set, the default will be midnight; if an illegal string is specified, a ValueError will result. (You can always later set the initial time with SetValue() after instantiation of the control.) -
    size
    The size of the control will be automatically adjusted for 12/24 hour format if wxDefaultSize is specified. -
    fmt24hr
    If true, control will display time in 24 hour time format; if false, it will use 12 hour AM/PM format. SetValue() will adjust values accordingly for the control, based on the format specified. -
    spinButton
    If specified, this button's events will be bound to the behavior of the wxTimeCtrl, working like up/down cursor key events. (See BindSpinButton.) -
    style
    By default, wxTimeCtrl will process TAB events, by allowing tab to the different cells within the control. +
    value +
    If no initial value is set, the default will be midnight; if an illegal string + is specified, a ValueError will result. (You can always later set the initial time + with SetValue() after instantiation of the control.) +
    size +
    The size of the control will be automatically adjusted for 12/24 hour format + if wxDefaultSize is specified. +
    +
    fmt24hr +
    If true, control will display time in 24 hour time format; if false, it will + use 12 hour AM/PM format. SetValue() will adjust values accordingly for the + control, based on the format specified. +
    +
    spinButton +
    If specified, this button's events will be bound to the behavior of the + wxTimeCtrl, working like up/down cursor key events. (See BindSpinButton.) +
    +
    style +
    By default, wxTimeCtrl will process TAB events, by allowing tab to the + different cells within the control.
-
SetValue(time_string)
Sets the value of the control to a particular time, given a valid time string; raises ValueError on invalid value -
GetValue()
Retrieves the string value of the time from the control
-
SetWxDateTime(wxDateTime)
Uses the time portion of a wxDateTime to construct a value for the control. -
GetWxDateTime()
Retrieves the value of the control, and applies it to the wxDateTimeFromHMS() constructor, and returns the resulting value. (This returns the date portion as "today".)
-
SetMxDateTime(mxDateTime)
Uses the time portion of an mxDateTime to construct a value for the control. NOTE: This imports mx.DateTime at runtime only if this or the Get function is called. -
GetMxDateTime()
Retrieves the value of the control and applies it to the DateTime.Time() constructor, and returns the resulting value. (mxDateTime is smart enough to know this is just a time value.) +
SetValue(time_string) +
Sets the value of the control to a particular time, given a valid time string; +raises ValueError on invalid value
-
BindSpinButton(wxSpinBtton)
Binds an externally created spin button to the control, so that up/down spin events change the active cell or selection in the control (in addition to the up/down cursor keys.) (This is primarily to allow you to create a "standard" interface to time controls, as seen in Windows.) +
GetValue() +
Retrieves the string value of the time from the control
-
EVT_TIMEUPDATE(win, id, func)
func is fired whenever the value of the control changes. +
SetWxDateTime(wxDateTime) +
Uses the time portion of a wxDateTime to construct a value for the control. +
+
GetWxDateTime() +
Retrieves the value of the control, and applies it to the wxDateTimeFromHMS() +constructor, and returns the resulting value. (This returns the date portion as +"today".) +
+
SetMxDateTime(mxDateTime) +
Uses the time portion of an mxDateTime to construct a value for the control. +NOTE: This imports mx.DateTime at runtime only if this or the Get function +is called. +
+
GetMxDateTime() +
Retrieves the value of the control and applies it to the DateTime.Time() +constructor, and returns the resulting value. (mxDateTime is smart enough to +know this is just a time value.) +
+
BindSpinButton(wxSpinBtton) +
Binds an externally created spin button to the control, so that up/down spin +events change the active cell or selection in the control (in addition to the +up/down cursor keys.) (This is primarily to allow you to create a "standard" +interface to time controls, as seen in Windows.) +
+
EVT_TIMEUPDATE(win, id, func) +
func is fired whenever the value of the control changes.
""" - if __name__ == '__main__': import sys,os import run run.main(['', os.path.basename(sys.argv[0])]) - - diff --git a/wxPython/wxPython/lib/timectrl.py b/wxPython/wxPython/lib/timectrl.py index 37c15e8c92..d32e17d825 100644 --- a/wxPython/wxPython/lib/timectrl.py +++ b/wxPython/wxPython/lib/timectrl.py @@ -3,6 +3,7 @@ # Author: Will Sadkin # Created: 09/19/2002 # Copyright: (c) 2002 by Will Sadkin, 2002 +# RCS-ID: $Id$ # License: wxWindows license #---------------------------------------------------------------------------- # NOTE: @@ -26,22 +27,24 @@ import string _debug = 0 _indent = 0 -def _dbg(*args, **kwargs): - global _indent +if _debug: + def _dbg(*args, **kwargs): + global _indent - if _debug: if len(args): if _indent: print ' ' * 3 * _indent, for arg in args: print arg, print - # else do nothing - - # post process args: - for kwarg, value in kwargs.items(): - if kwarg == 'indent' and value: _indent = _indent + 1 - elif kwarg == 'indent' and value == 0: _indent = _indent - 1 - if _indent < 0: _indent = 0 + # else do nothing + # post process args: + for kwarg, value in kwargs.items(): + if kwarg == 'indent' and value: _indent = _indent + 1 + elif kwarg == 'indent' and value == 0: _indent = _indent - 1 + if _indent < 0: _indent = 0 +else: + def _dbg(*args, **kwargs): + pass # This class of event fires whenever the value of the time changes in the control: @@ -55,10 +58,40 @@ class TimeUpdatedEvent(wxPyCommandEvent): wxPyCommandEvent.__init__(self, wxEVT_TIMEVAL_UPDATED, id) self.value = value def GetValue(self): - """Retrieve the value of the float control at the time this event was generated""" + """Retrieve the value of the time control at the time this event was generated""" return self.value +# Set up all the positions of the cells in the wxTimeCtrl (once at module import): +# Format of control is: +# hh:mm:ss xM +# 1 +# positions: 01234567890 +_listCells = ['hour', 'minute', 'second', 'am_pm'] +_listCellRange = [(0,1,2), (3,4,5), (6,7,8), (9,10,11)] +_listDelimPos = [2,5,8] + +# Create dictionary of cell ranges, indexed by name or position in the range: +_dictCellRange = {} +for i in range(4): + _dictCellRange[_listCells[i]] = _listCellRange[i] +for cell in _listCells: + for i in _dictCellRange[cell]: + _dictCellRange[i] = _dictCellRange[cell] + + +# Create lists of starting and ending positions for each range, and a dictionary of starting +# positions indexed by name +_listStartCellPos = [] +_listEndCellPos = [] +for tup in _listCellRange: + _listStartCellPos.append(tup[0]) # 1st char of cell + _listEndCellPos.append(tup[1]) # last char of cell (not including delimiter) + +_dictStartCellPos = {} +for i in range(4): + _dictStartCellPos[_listCells[i]] = _listStartCellPos[i] + class wxTimeCtrl(wxTextCtrl): def __init__ ( @@ -72,46 +105,16 @@ class wxTimeCtrl(wxTextCtrl): pos=pos, size=size, style=style, name=name) self.__fmt24hr = fmt24hr + if size == wxDefaultSize: # set appropriate default sizes depending on format: if self.__fmt24hr: - testText = '00:00:00 ' + testText = '00:00:00X' # give it a little extra space else: - testText = '00:00:00 XXX' + testText = '00:00:00 XXX' # give it a little extra space w, h = self.GetTextExtent(testText) self.SetClientSize( (w+4, self.GetClientSize().height) ) - # Set up all the positions of the cells in the wxTimeCtrl (once): - # Format of control is: - # hh:mm:ss xM - # 1 - # positions: 01234567890 - - self.__listCells = ['hour', 'minute', 'second', 'am_pm'] - self.__listCellRange = [(0,1,2), (3,4,5), (6,7,8), (9,10,11)] - self.__listDelimPos = [2,5,8] - - # Create dictionary of cell ranges, indexed by name or position in the range: - self.__dictCellRange = {} - for i in range(4): - self.__dictCellRange[self.__listCells[i]] = self.__listCellRange[i] - - for cell in self.__listCells: - for i in self.__dictCellRange[cell]: - self.__dictCellRange[i] = self.__dictCellRange[cell] - - - # Create lists of starting and ending positions for each range, and a dictionary of starting - # positions indexed by name - self.__listStartCellPos = [] - self.__listEndCellPos = [] - for tup in self.__listCellRange: - self.__listStartCellPos.append(tup[0]) # 1st char of cell - self.__listEndCellPos.append(tup[1]) # last char of cell (not including delimiter) - - self.__dictStartCellPos = {} - for i in range(4): - self.__dictStartCellPos[self.__listCells[i]] = self.__listStartCellPos[i] if self.__fmt24hr: self.__lastCell = 'second' else: self.__lastCell = 'am_pm' @@ -123,27 +126,48 @@ class wxTimeCtrl(wxTextCtrl): self.SetValue('12:00:00 AM') # set initial position and selection state - self.__SetCurrentCell(self.__dictStartCellPos['hour']) + self.__SetCurrentCell(_dictStartCellPos['hour']) self.__bSelection = false + self.__posSelectTo = self.__posCurrent + # Set up internal event handlers to change the event reaction behavior of + # the base wxTextCtrl: EVT_TEXT(self, self.GetId(), self.__OnTextChange) EVT_SET_FOCUS(self, self.__OnFocus) + EVT_LEFT_UP(self, self.__OnChangePos) + EVT_LEFT_DCLICK(self, self.__OnDoubleClick) EVT_CHAR(self, self.__OnChar) if spinButton: self.BindSpinbutton(spinButton) # bind spin button up/down events to this control + + def BindSpinButton(self, sb): + """ + This function binds an externally created spin button to the control, so that + up/down events from the button automatically change the control. + """ + _dbg('wxTimeCtrl::BindSpinButton') + self.__spinButton = sb + if self.__spinButton: + # bind event handlers to spin ctrl + EVT_SPIN_UP(self.__spinButton, self.__spinButton.GetId(), self.__OnSpinUp) + EVT_SPIN_DOWN(self.__spinButton, self.__spinButton.GetId(), self.__OnSpinDown) + + + def __repr__(self): return "" % self.GetValue() + def SetValue(self, value): """ Validating SetValue function for time strings, doing 12/24 format conversion as appropriate. """ _dbg('wxTimeCtrl::SetValue', indent=1) - dict_range = self.__dictCellRange - dict_start = self.__dictStartCellPos + dict_range = _dictCellRange # (for brevity) + dict_start = _dictStartCellPos fmt12len = dict_range['am_pm'][-1] fmt24len = dict_range['second'][-1] @@ -169,7 +193,6 @@ class wxTimeCtrl(wxTextCtrl): if len_ok and hour_ok and min_ok and sec_ok and separators_correct: _dbg('valid time string') - self.__hour = hour if len(value) == fmt12len: # handle 12 hour format conversion for actual hour: am = value[dict_start['am_pm']:] == 'AM' @@ -187,7 +210,6 @@ class wxTimeCtrl(wxTextCtrl): _dbg('need_to_convert =', need_to_convert) if need_to_convert: #convert to 12/24 hour format as specified: - dict_start = self.__dictStartCellPos if self.__fmt24hr and len(value) == fmt12len: text = '%.2d:%.2d:%.2d' % (hour, minute, second) else: @@ -218,7 +240,7 @@ class wxTimeCtrl(wxTextCtrl): raise ValueError, 'value is not a valid time string' _dbg(indent=0) - def SetFromWxDateTime(self, wxdt): + def SetWxDateTime(self, wxdt): value = '%2d:%.2d:%.2d' % (wxdt.GetHour(), wxdt.GetMinute(), wxdt.GetSecond()) self.SetValue(value) @@ -236,18 +258,6 @@ class wxTimeCtrl(wxTextCtrl): t = DateTime.Time(self.__hour, self.__minute, self.__second) return t - def BindSpinButton(self, sb): - """ - This function binds an externally created spin button to the control, so that - up/down events from the button automatically change the control. - """ - _dbg('wxTimeCtrl::BindSpinButton') - self.__spinButton = sb - if self.__spinButton: - EVT_SPIN_UP(self.__spinButton, self.__spinButton.GetId(), self.__OnSpinUp) # bind event handler to spin ctrl - EVT_SPIN_DOWN(self.__spinButton, self.__spinButton.GetId(), self.__OnSpinDown) # bind event handler to spin ctrl - - #------------------------------------------------------------------------------------------------------------- # these are private functions and overrides: @@ -256,7 +266,8 @@ class wxTimeCtrl(wxTextCtrl): Sets state variables that indicate the current cell and position within the control. """ self.__posCurrent = pos - self.__cellStart, self.__cellEnd = self.__dictCellRange[pos][0], self.__dictCellRange[pos][-1] + self.__cellStart, self.__cellEnd = _dictCellRange[pos][0], _dictCellRange[pos][-1] + def SetInsertionPoint(self, pos): """ @@ -266,6 +277,54 @@ class wxTimeCtrl(wxTextCtrl): wxTextCtrl.SetInsertionPoint(self, pos) # (causes EVT_TEXT event to fire) + def SetSelection(self, sel_start, sel_to): + _dbg('wxTimeCtrl::SetSelection', sel_start, sel_to) + self.__bSelection = sel_start != sel_to + self.__posSelectTo = sel_to + wxTextCtrl.SetSelection(self, sel_start, sel_to) + + + def __OnFocus(self,event): + """ + This event handler is currently necessary to work around new default + behavior as of wxPython2.3.3; + The TAB key auto selects the entire contents of the wxTextCtrl *after* + the EVT_SET_FOCUS event occurs; therefore we can't query/adjust the selection + *here*, because it hasn't happened yet. So to prevent this behavior, and + preserve the correct selection when the focus event is not due to tab, + we need to pull the following trick: + """ + _dbg('wxTimeCtrl::OnFocus') + wxCallAfter(self.__FixSelection) + + + def __FixSelection(self): + """ + This gets called after the TAB traversal selection is made, if the + focus event was due to this, but before the EVT_LEFT_* events if + the focus shift was due to a mouse event. + + The trouble is that, a priori, there's no explicit notification of + why the focus event we received. However, the whole reason we need to + do this is because the default behavior on TAB traveral in a wxTextCtrl is + now to select the entire contents of the window, something we don't want. + So we can *now* test the selection range, and if it's "the whole text" + we can assume the cause, change the insertion point to the start of + the control, and deselect. + """ + _dbg('wxTimeCtrl::FixSelection', indent=1) + sel_start, sel_to = self.GetSelection() + if sel_start == 0 and sel_to in _dictCellRange[self.__lastCell]: + # This isn't normally allowed, and so assume we got here by the new + # "tab traversal" behavior, so we need to reset the selection + # and insertion point: + _dbg('entire text selected; resetting selection to start of control') + self.__SetCurrentCell(0) + self.SetInsertionPoint(0) + self.SetSelection(self.__posCurrent, self.__posCurrent) + _dbg(indent=0) + + def __OnTextChange(self, event): """ This private event handler is required to retain the current position information of the cursor @@ -284,17 +343,11 @@ class wxTimeCtrl(wxTextCtrl): event.Skip() _dbg(indent=0) - def __OnFocus(self, event): - """ - This internal event handler ensures legal setting of input cursor on (re)focus to the control. - """ - _dbg('wxTimeCtrl::OnFocus; ctrl id=', event.GetId()) - self.__SetCurrentCell(0) - self.__bSelection = false - self.__posSelectTo = self.__posCurrent + def __OnSpin(self, key): + self.IncrementValue(key, self.__posCurrent) + self.SetFocus() self.SetInsertionPoint(self.__posCurrent) - event.Skip() - + self.SetSelection(self.__posCurrent, self.__posSelectTo) def __OnSpinUp(self, event): """ @@ -302,9 +355,8 @@ class wxTimeCtrl(wxTextCtrl): causes control to behave as if up arrow was pressed. """ _dbg('wxTimeCtrl::OnSpinUp') - pos = self.GetInsertionPoint() - self.IncrementValue(WXK_UP, pos) - self.SetInsertionPoint(pos) + self.__OnSpin(WXK_UP) + def __OnSpinDown(self, event): """ @@ -312,9 +364,42 @@ class wxTimeCtrl(wxTextCtrl): causes control to behave as if down arrow was pressed. """ _dbg('wxTimeCtrl::OnSpinDown') + self.__OnSpin(WXK_DOWN) + + + def __OnChangePos(self, event): + """ + Event handler for left mouse click events; this handler + changes limits the selection to the new cell boundaries. + """ + _dbg('wxTimeCtrl::OnChangePos', indent=1) pos = self.GetInsertionPoint() - self.IncrementValue(WXK_DOWN, pos) - self.SetInsertionPoint(pos) + self.__SetCurrentCell(pos) + sel_start, sel_to = self.GetSelection() + selection = sel_start != sel_to + if not selection: + if pos in _listDelimPos: # disallow position at end of field: + self.__posCurrent = pos-1 + self.__posSelectTo = self.__posCurrent + self.__bSelection = false + else: + # only allow selection to end of current cell: + if sel_to < pos: self.__posSelectTo = self.__cellStart + elif sel_to > pos: self.__posSelectTo = self.__cellEnd + + _dbg('new pos =', pos, 'select to ', self.__posSelectTo, indent=0) + self.SetSelection(self.__posCurrent, self.__posSelectTo) + event.Skip() + + def __OnDoubleClick(self, event): + """ + Event handler for left double-click mouse events; this handler + causes the cell at the double-click point to be selected. + """ + _dbg('wxTimeCtrl::OnDoubleClick') + pos = self.GetInsertionPoint() + self.__SetCurrentCell(pos) + self.SetSelection(self.__cellStart, self.__cellEnd) def __OnChar(self, event): @@ -339,7 +424,7 @@ class wxTimeCtrl(wxTextCtrl): sel_start, sel_to = self.GetSelection() selection = sel_start != sel_to if not selection: - self.__bSelection = false # predict unselection of entire region + self.__bSelection = false # predict unselection of entire region _dbg('keycode = ', key) _dbg('pos = ', pos) @@ -350,8 +435,8 @@ class wxTimeCtrl(wxTextCtrl): elif key == WXK_TAB: # skip to next field if applicable: _dbg('key == WXK_TAB') - dict_range = self.__dictCellRange - dict_start = self.__dictStartCellPos + dict_range = _dictCellRange # (for brevity) + dict_start = _dictStartCellPos if event.ShiftDown(): # tabbing backwords ###(NOTE: doesn't work; wxTE_PROCESS_TAB doesn't appear to send us this event!) @@ -398,30 +483,30 @@ class wxTimeCtrl(wxTextCtrl): if sel_to != pos: if sel_to - 1 == pos: # allow unselection of position self.__bSelection = false # predict unselection of entire region + self.__posSelectTo = pos event.Skip() - if pos in self.__listStartCellPos: # can't select pass delimiters + if pos in _listStartCellPos: # can't select pass delimiters _dbg(indent=0) return - elif pos in self.__listEndCellPos: # can't use normal selection, because position ends up + elif pos in _listEndCellPos: # can't use normal selection, because position ends up # at delimeter _dbg('set selection from', pos-1, 'to', self.__posCurrent) - self.__bSelection = true - self.__posSelectTo = pos self.__posCurrent = pos-1 - self.SetSelection(self.__posCurrent, self.__posSelectTo) + self.SetSelection(self.__posCurrent, pos) _dbg(indent=0) return - else: event.Skip() # allow selection + else: + self.__posSelectTo = sel_to - 1 # predict change in selection + event.Skip() # allow selection # else... not selecting if selection: _dbg('sel_start=', sel_start, 'sel_to=', sel_to, '(Clearing selection)') self.SetSelection(pos,pos) # clear selection - self.__bSelection = false if pos == 0: # let base ctrl handle left bound case event.Skip() - elif pos in self.__listStartCellPos: # skip (left) OVER the colon/space: + elif pos in _listStartCellPos: # skip (left) OVER the colon/space: self.SetInsertionPoint(pos-1) # (this causes a EVT_TEXT) self.__SetCurrentCell(pos-2) # set resulting position as "current" else: @@ -433,15 +518,13 @@ class wxTimeCtrl(wxTextCtrl): _dbg('key == WXK_RIGHT') if event.ShiftDown(): _dbg('event.ShiftDown()') - if sel_to in self.__listDelimPos: # can't select pass delimiters + if sel_to in _listDelimPos: # can't select pass delimiters _dbg(indent=0) return - elif pos in self.__listEndCellPos: # can't use normal selection, because position ends up + elif pos in _listEndCellPos: # can't use normal selection, because position ends up # at delimeter _dbg('set selection from', self.__posCurrent, 'to', pos+1) - self.__bSelection = true - self.__posSelectTo = pos+1 - self.SetSelection(self.__posCurrent, self.__posSelectTo) + self.SetSelection(self.__posCurrent, pos+1) _dbg(indent=0) return else: event.Skip() @@ -451,26 +534,31 @@ class wxTimeCtrl(wxTextCtrl): _dbg('sel_start=', sel_start, 'sel_to=', sel_to, '(Clearing selection)') pos = sel_start self.SetSelection(pos,pos) # clear selection - self.__bSelection = false - if pos == self.__dictStartCellPos[self.__lastCell]+1: + if pos == _dictStartCellPos[self.__lastCell]+1: _dbg(indent=0) return # don't allow cursor past last cell - if pos in self.__listEndCellPos: # skip (right) OVER the colon/space: + if pos in _listEndCellPos: # skip (right) OVER the colon/space: self.SetInsertionPoint(pos+1) # (this causes a EVT_TEXT) self.__SetCurrentCell(pos+2) # set resulting position else: self.__SetCurrentCell(pos+1) # record the new cell position after the event is finished - self.__bSelection = false event.Skip() elif key in (WXK_UP, WXK_DOWN): _dbg('key in (WXK_UP, WXK_DOWN)') self.IncrementValue(key, pos) # increment/decrement as appropriate - self.SetInsertionPoint(pos) + elif key < WXK_SPACE or key == WXK_DELETE or key > 255: event.Skip() # non alphanumeric; process normally (Right thing to do?) + elif chr(key) in ['!', 'c', 'C']: # Special character; sets the value of the control to "now" + _dbg("key == '!'; setting time to 'now'") + now = wxDateTime_Now() + self.SetWxDateTime(now) + _dbg(indent=0) + return + elif chr(key) in string.digits: # let ChangeValue validate and update current position self.ChangeValue(chr(key), pos) # handle event (and swallow it) @@ -482,6 +570,7 @@ class wxTimeCtrl(wxTextCtrl): return _dbg(indent=0) + def IncrementValue(self, key, pos): _dbg('wxTimeCtrl::IncrementValue', key, pos) text = self.GetValue() @@ -490,15 +579,16 @@ class wxTimeCtrl(wxTextCtrl): selection = sel_start != sel_to cell_selected = selection and sel_to -1 != pos - dict_start = self.__dictStartCellPos + dict_start = _dictStartCellPos # (for brevity) # Determine whether we should change the entire cell or just a portion of it: - if( not selection - or cell_selected - or text[pos] == ' ' - or text[pos] == '9' and text[pos-1] == ' ' and key == WXK_UP - or text[pos] == '1' and text[pos-1] == ' ' and key == WXK_DOWN + if( cell_selected + or (pos in _listStartCellPos and not selection) + or (text[pos] == ' ' and text[pos+1] not in ('1', '2')) + or (text[pos] == '9' and text[pos-1] == ' ' and key == WXK_UP) + or (text[pos] == '1' and text[pos-1] == ' ' and key == WXK_DOWN) or pos >= dict_start['am_pm']): + _dbg(indent=1) self.IncrementCell(key, pos) _dbg(indent=0) @@ -511,8 +601,8 @@ class wxTimeCtrl(wxTextCtrl): else: digit = ' ' else: if pos == dict_start['hour']: - if int(text[pos + 1]) >3: mod = 2 # allow for 20-23 - else: mod = 3 # allow 00-19 + if int(text[pos + 1]) >3: mod = 2 # allow for 20-23 + else: mod = 3 # allow 00-19 elif pos == dict_start['hour'] + 1: if self.__fmt24hr: if text[pos - 1] == '2': mod = 4 # allow hours 20-23 @@ -533,13 +623,12 @@ class wxTimeCtrl(wxTextCtrl): _dbg(indent=0) - def IncrementCell(self, key, pos): _dbg('wxTimeCtrl::IncrementCell', key, pos) self.__SetCurrentCell(pos) # determine current cell hour, minute, second = self.__hour, self.__minute, self.__second text = self.GetValue() - dict_start = self.__dictStartCellPos + dict_start = _dictStartCellPos # (for brevity) if key == WXK_UP: inc = 1 else: inc = -1 @@ -558,11 +647,10 @@ class wxTimeCtrl(wxTextCtrl): newvalue = '%.2d:%.2d:%.2d' % (hour, minute, second) self.__posCurrent = self.__cellStart - self.__posSelectTo = self.__cellEnd - self.__bSelection = true _dbg(indent=1) self.SetValue(newvalue) _dbg(indent=0) + self.SetSelection(self.__cellStart, self.__cellEnd) def ChangeValue(self, char, pos): @@ -574,10 +662,11 @@ class wxTimeCtrl(wxTextCtrl): self.__posSelectTo = sel_to self.__bSelection = selection = sel_start != sel_to cell_selected = selection and sel_to -1 != pos + _dbg('cell_selected =', cell_selected) - dict_start = self.__dictStartCellPos + dict_start = _dictStartCellPos # (for brevity) - if pos in self.__listDelimPos: return # don't allow change of punctuation + if pos in _listDelimPos: return # don't allow change of punctuation elif( 0 < pos < dict_start['am_pm'] and char not in string.digits): return # AM/PM not allowed in this position @@ -588,25 +677,28 @@ class wxTimeCtrl(wxTextCtrl): if pos == hour_start: # if at 1st position, if self.__fmt24hr: # and using 24 hour format - if char not in ('0', '1', '2'): # return if digit not 0,1, or 2 - return - if cell_selected: # replace cell contents + if cell_selected: # replace cell contents with hour represented by digit newtext = '%.2d' % int(char) + text[hour_start+2:] + elif char not in ('0', '1', '2'): # return if digit not 0,1, or 2 + return else: # relace current position newtext = char + text[pos+1:] else: # (12 hour format) - if char not in ('1', ' '): # can only type a 1 or space - return - if text[pos+1] not in ('0', '1', '2'): # and then, only if other column is 0,1, or 2 - return - if( char == ' ' # and char isn't space and - and (cell_selected or text[pos+1] == '0')): # 2nd column is 0 or cell isn't selected - return - # else... ok - if cell_selected: # replace cell contents - newtext = '%2d' % int(char) + text[hour_start+2:] - else: # relace current position - newtext = char + text[pos+1:] + if cell_selected: + if char == ' ': return # can't erase entire cell + elif char == '0': # treat 0 as '12' + newtext = '12' + text[hour_start+2:] + else: # replace cell contents with hour represented by digit + newtext = '%2d' % int(char) + text[hour_start+2:] + else: + if char not in ('1', ' '): # can only type a 1 or space + return + if text[pos+1] not in ('0', '1', '2'): # and then, only if other column is 0,1, or 2 + return + if char == ' ' and text[pos+1] == '0': # and char isn't space if 2nd column is 0 + return + else: # ok; replace current position + newtext = char + text[pos+1:] if char == ' ': self.SetInsertionPoint(pos+1) # move insert point to legal position elif pos == hour_start+1: # if editing 2nd position of hour @@ -634,16 +726,17 @@ class wxTimeCtrl(wxTextCtrl): else: return # not a valid position # update member position vars and set selection to character changed - self.__posCurrent = pos+1 - self.__SetCurrentCell(self.__posCurrent) + if not cell_selected: + _dbg('selecting current digit') + self.SetSelection(self.__posCurrent, pos+1) _dbg(indent=1) _dbg('newtext=', newtext) _dbg(indent=0) self.SetValue(newtext) - self.SetInsertionPoint(pos+1) + self.SetSelection(self.__posCurrent, self.__posSelectTo) #---------------------------------------------------------------------------- - +# Test jig for wxTimeCtrl: if __name__ == '__main__': import traceback @@ -654,14 +747,16 @@ if __name__ == '__main__': fmt24hr = 0, test_mx = 0, style = wxTAB_TRAVERSAL ): - self.test_mx = test_mx wxPanel.__init__(self, parent, id, pos, size, style) - sizer = wxBoxSizer( wxHORIZONTAL ) + self.test_mx = test_mx + self.tc = wxTimeCtrl(self, 10, fmt24hr = fmt24hr) - sizer.AddWindow( self.tc, 0, wxALIGN_CENTRE|wxLEFT|wxTOP|wxBOTTOM, 5 ) sb = wxSpinButton( self, 20, wxDefaultPosition, wxSize(-1,20), 0 ) self.tc.BindSpinButton(sb) + + sizer = wxBoxSizer( wxHORIZONTAL ) + sizer.AddWindow( self.tc, 0, wxALIGN_CENTRE|wxLEFT|wxTOP|wxBOTTOM, 5 ) sizer.AddWindow( sb, 0, wxALIGN_CENTRE|wxRIGHT|wxTOP|wxBOTTOM, 5 ) self.SetAutoLayout( true ) @@ -686,7 +781,7 @@ if __name__ == '__main__': fmt24hr = '24' in sys.argv test_mx = 'mx' in sys.argv try: - frame = wxFrame(NULL, -1, "Junk", wxPoint(20,20), wxSize(100,100) ) + frame = wxFrame(NULL, -1, "wxTimeCtrl Test", wxPoint(20,20), wxSize(100,100) ) panel = TestPanel(frame, -1, wxPoint(-1,-1), fmt24hr=fmt24hr, test_mx = test_mx) frame.Show(true) except: