wxTimeCtrl updates from Will Sadkin

git-svn-id: https://svn.wxwidgets.org/svn/wx/wxWidgets/branches/WX_2_4_BRANCH@17369 c3d73ce0-8a6f-49c7-b76d-6d57e0e08775
This commit is contained in:
Robin Dunn
2002-09-24 17:57:49 +00:00
parent b5177a3fe4
commit 28825fa22a
2 changed files with 389 additions and 198 deletions

View File

@@ -1,78 +1,139 @@
from wxPython.wx import * from wxPython.wx import *
from wxPython.lib.timectrl import * from wxPython.lib.timectrl import *
import string
#---------------------------------------------------------------------- #----------------------------------------------------------------------
class TestPanel(wxPanel): class TestPanel( wxPanel ):
def __init__(self, parent, log): def __init__( self, parent, log ):
wxPanel.__init__(self, parent, -1)
wxPanel.__init__( self, parent, -1 )
self.log = log self.log = log
panel = wxPanel(self, -1) panel = wxPanel( self, -1 )
grid = wxFlexGridSizer( 0, 2, 20, 0 ) grid = wxFlexGridSizer( 0, 2, 20, 0 )
text1 = wxStaticText( panel, 10, "A 12-hour format wxTimeCtrl:", wxDefaultPosition, wxDefaultSize, 0 ) text1 = wxStaticText( panel, 10, "A 12-hour format wxTimeCtrl:")
grid.AddWindow( text1, 0, wxALIGN_RIGHT|wxALIGN_CENTER_VERTICAL, 5 ) self.time12 = wxTimeCtrl( panel, 20, name="12 hour control" )
hsizer1 = wxBoxSizer( wxHORIZONTAL )
self.time12 = wxTimeCtrl(panel, 20, name="12 hour time")
hsizer1.AddWindow( self.time12, 0, wxALIGN_CENTRE, 5 )
spin1 = wxSpinButton( panel, 30, wxDefaultPosition, wxSize(-1,20), 0 ) spin1 = wxSpinButton( panel, 30, wxDefaultPosition, wxSize(-1,20), 0 )
self.time12.BindSpinButton(spin1) self.time12.BindSpinButton( spin1 )
hsizer1.AddWindow( spin1, 0, wxALIGN_CENTRE, 5 )
grid.AddSizer( hsizer1, 0, wxALIGN_CENTER_VERTICAL|wxLEFT, 5 )
text2 = wxStaticText( panel, 40, "A 24-hour format wxTimeCtrl:", wxDefaultPosition, wxDefaultSize, 0 ) grid.AddWindow( text1, 0, wxALIGN_RIGHT, 5 )
grid.AddWindow( text2, 0, wxALIGN_RIGHT|wxALIGN_CENTER_VERTICAL|wxLEFT|wxTOP|wxBOTTOM, 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") text2 = wxStaticText( panel, 40, "A 24-hour format wxTimeCtrl:")
hsizer2.AddWindow( self.time24, 0, wxALIGN_CENTRE, 5 ) self.time24 = wxTimeCtrl( panel, 50, fmt24hr=true, name="24 hour control" )
spin2 = wxSpinButton( panel, 60, wxDefaultPosition, wxSize(-1,20), 0 ) spin2 = wxSpinButton( panel, 60, wxDefaultPosition, wxSize(-1,20), 0 )
self.time24.BindSpinButton(spin2) self.time24.BindSpinButton( spin2 )
hsizer2.AddWindow( spin2, 0, wxALIGN_CENTRE, 5 )
grid.AddSizer( hsizer2, 0, wxALIGN_CENTER_VERTICAL|wxLEFT, 5 )
panel.SetAutoLayout(true) grid.AddWindow( text2, 0, wxALIGN_RIGHT|wxTOP|wxBOTTOM, 5 )
panel.SetSizer(grid) hbox2 = wxBoxSizer( wxHORIZONTAL )
grid.Fit(panel) hbox2.AddWindow( self.time24, 0, wxALIGN_CENTRE, 5 )
panel.Move((50,50)) 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 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): def OnTimeChange( self, event ):
timectrl = self.panel.FindWindowById(event.GetId()) timectrl = self.panel.FindWindowById( event.GetId() )
self.log.write('%s = %s\n' % ( self.log.write('%s time = %s\n' % ( timectrl.GetName(), timectrl.GetValue() ) )
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): def runTest( frame, nb, log ):
win = TestPanel(nb, log) win = TestPanel( nb, log )
return win 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 = """<html><body> overview = """<html><body>
<P> <P>
<B>wxTimeCtrl</B> 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. <B>wxTimeCtrl</B> 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.
<P> <P>
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.
<P> <P>
Here's the interface for wxTimeCtrl: The <B>!</B> key sets the value of the control to <B><I>now!</I></B>
<DL> <P>
Here's the API for wxTimeCtrl:
<PRE> <DL><PRE>
<B>wxTimeCtrl</B>(parent, id = -1, <B>wxTimeCtrl</B>(
parent, id = -1,
<B>value</B> = '12:00:00 AM', <B>value</B> = '12:00:00 AM',
pos = wxDefaultPosition, pos = wxDefaultPosition,
size = wxDefaultSize, size = wxDefaultSize,
@@ -82,36 +143,71 @@ Here's the interface for wxTimeCtrl:
name = "time") name = "time")
</PRE> </PRE>
<UL> <UL>
<DT><B>value</B><DD>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.) <DT><B>value</B>
<DL><B>size</B><DD>The size of the control will be automatically adjusted for 12/24 hour format if wxDefaultSize is specified. <DD>If no initial value is set, the default will be midnight; if an illegal string
<DT><B>fmt24hr</B><DD>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. is specified, a ValueError will result. (You can always later set the initial time
<DT><B>spinButton</B><DD>If specified, this button's events will be bound to the behavior of the wxTimeCtrl, working like up/down cursor key events. (See BindSpinButton.) with SetValue() after instantiation of the control.)
<DT><B>style</B><DD>By default, wxTimeCtrl will process TAB events, by allowing tab to the different cells within the control. <DL><B>size</B>
<DD>The size of the control will be automatically adjusted for 12/24 hour format
if wxDefaultSize is specified.
<BR>
<DT><B>fmt24hr</B>
<DD>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.
<BR>
<DT><B>spinButton</B>
<DD>If specified, this button's events will be bound to the behavior of the
wxTimeCtrl, working like up/down cursor key events. (See BindSpinButton.)
<BR>
<DT><B>style</B>
<DD>By default, wxTimeCtrl will process TAB events, by allowing tab to the
different cells within the control.
</DL> </DL>
</UL> </UL>
<DT><B>SetValue(time_string)</B><DD>Sets the value of the control to a particular time, given a valid time string; raises ValueError on invalid value
<DT><B>GetValue()</B><DD>Retrieves the string value of the time from the control
<BR> <BR>
<DT><B>SetWxDateTime(wxDateTime)</B><DD>Uses the time portion of a wxDateTime to construct a value for the control.
<DT><B>GetWxDateTime()</B><DD>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".)
<BR> <BR>
<DT><B>SetMxDateTime(mxDateTime)</B><DD>Uses the time portion of an mxDateTime to construct a value for the control. <EM>NOTE:</EM> This imports mx.DateTime at runtime only if this or the Get function is called. <DT><B>SetValue(time_string)</B>
<DT><B>GetMxDateTime()</B><DD>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.) <DD>Sets the value of the control to a particular time, given a valid time string;
raises ValueError on invalid value
<BR> <BR>
<DT><B>BindSpinButton(wxSpinBtton)</B><DD>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.) <DT><B>GetValue()</B>
<DD>Retrieves the string value of the time from the control
<BR> <BR>
<DT><B>EVT_TIMEUPDATE(win, id, func)</B><DD>func is fired whenever the value of the control changes. <DT><B>SetWxDateTime(wxDateTime)</B>
<DD>Uses the time portion of a wxDateTime to construct a value for the control.
<BR>
<DT><B>GetWxDateTime()</B>
<DD>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".)
<BR>
<DT><B>SetMxDateTime(mxDateTime)</B>
<DD>Uses the time portion of an mxDateTime to construct a value for the control.
<EM>NOTE:</EM> This imports mx.DateTime at runtime only if this or the Get function
is called.
<BR>
<DT><B>GetMxDateTime()</B>
<DD>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.)
<BR>
<DT><B>BindSpinButton(wxSpinBtton)</B>
<DD>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.)
<BR>
<DT><B>EVT_TIMEUPDATE(win, id, func)</B>
<DD>func is fired whenever the value of the control changes.
</DL> </DL>
</body></html> </body></html>
""" """
if __name__ == '__main__': if __name__ == '__main__':
import sys,os import sys,os
import run import run
run.main(['', os.path.basename(sys.argv[0])]) run.main(['', os.path.basename(sys.argv[0])])

View File

@@ -3,6 +3,7 @@
# Author: Will Sadkin # Author: Will Sadkin
# Created: 09/19/2002 # Created: 09/19/2002
# Copyright: (c) 2002 by Will Sadkin, 2002 # Copyright: (c) 2002 by Will Sadkin, 2002
# RCS-ID: $Id$
# License: wxWindows license # License: wxWindows license
#---------------------------------------------------------------------------- #----------------------------------------------------------------------------
# NOTE: # NOTE:
@@ -26,22 +27,24 @@ import string
_debug = 0 _debug = 0
_indent = 0 _indent = 0
def _dbg(*args, **kwargs): if _debug:
global _indent def _dbg(*args, **kwargs):
global _indent
if _debug:
if len(args): if len(args):
if _indent: print ' ' * 3 * _indent, if _indent: print ' ' * 3 * _indent,
for arg in args: print arg, for arg in args: print arg,
print print
# else do nothing # 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
# 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: # 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) wxPyCommandEvent.__init__(self, wxEVT_TIMEVAL_UPDATED, id)
self.value = value self.value = value
def GetValue(self): 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 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): class wxTimeCtrl(wxTextCtrl):
def __init__ ( def __init__ (
@@ -72,46 +105,16 @@ class wxTimeCtrl(wxTextCtrl):
pos=pos, size=size, style=style, name=name) pos=pos, size=size, style=style, name=name)
self.__fmt24hr = fmt24hr self.__fmt24hr = fmt24hr
if size == wxDefaultSize: if size == wxDefaultSize:
# set appropriate default sizes depending on format: # set appropriate default sizes depending on format:
if self.__fmt24hr: if self.__fmt24hr:
testText = '00:00:00 ' testText = '00:00:00X' # give it a little extra space
else: else:
testText = '00:00:00 XXX' testText = '00:00:00 XXX' # give it a little extra space
w, h = self.GetTextExtent(testText) w, h = self.GetTextExtent(testText)
self.SetClientSize( (w+4, self.GetClientSize().height) ) 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' if self.__fmt24hr: self.__lastCell = 'second'
else: self.__lastCell = 'am_pm' else: self.__lastCell = 'am_pm'
@@ -123,27 +126,48 @@ class wxTimeCtrl(wxTextCtrl):
self.SetValue('12:00:00 AM') self.SetValue('12:00:00 AM')
# set initial position and selection state # set initial position and selection state
self.__SetCurrentCell(self.__dictStartCellPos['hour']) self.__SetCurrentCell(_dictStartCellPos['hour'])
self.__bSelection = false 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_TEXT(self, self.GetId(), self.__OnTextChange)
EVT_SET_FOCUS(self, self.__OnFocus) EVT_SET_FOCUS(self, self.__OnFocus)
EVT_LEFT_UP(self, self.__OnChangePos)
EVT_LEFT_DCLICK(self, self.__OnDoubleClick)
EVT_CHAR(self, self.__OnChar) EVT_CHAR(self, self.__OnChar)
if spinButton: if spinButton:
self.BindSpinbutton(spinButton) # bind spin button up/down events to this control 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): def __repr__(self):
return "<wxTimeCtrl: %s>" % self.GetValue() return "<wxTimeCtrl: %s>" % self.GetValue()
def SetValue(self, value): def SetValue(self, value):
""" """
Validating SetValue function for time strings, doing 12/24 format conversion as appropriate. Validating SetValue function for time strings, doing 12/24 format conversion as appropriate.
""" """
_dbg('wxTimeCtrl::SetValue', indent=1) _dbg('wxTimeCtrl::SetValue', indent=1)
dict_range = self.__dictCellRange dict_range = _dictCellRange # (for brevity)
dict_start = self.__dictStartCellPos dict_start = _dictStartCellPos
fmt12len = dict_range['am_pm'][-1] fmt12len = dict_range['am_pm'][-1]
fmt24len = dict_range['second'][-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: if len_ok and hour_ok and min_ok and sec_ok and separators_correct:
_dbg('valid time string') _dbg('valid time string')
self.__hour = hour self.__hour = hour
if len(value) == fmt12len: # handle 12 hour format conversion for actual hour: if len(value) == fmt12len: # handle 12 hour format conversion for actual hour:
am = value[dict_start['am_pm']:] == 'AM' am = value[dict_start['am_pm']:] == 'AM'
@@ -187,7 +210,6 @@ class wxTimeCtrl(wxTextCtrl):
_dbg('need_to_convert =', need_to_convert) _dbg('need_to_convert =', need_to_convert)
if need_to_convert: #convert to 12/24 hour format as specified: if need_to_convert: #convert to 12/24 hour format as specified:
dict_start = self.__dictStartCellPos
if self.__fmt24hr and len(value) == fmt12len: if self.__fmt24hr and len(value) == fmt12len:
text = '%.2d:%.2d:%.2d' % (hour, minute, second) text = '%.2d:%.2d:%.2d' % (hour, minute, second)
else: else:
@@ -218,7 +240,7 @@ class wxTimeCtrl(wxTextCtrl):
raise ValueError, 'value is not a valid time string' raise ValueError, 'value is not a valid time string'
_dbg(indent=0) _dbg(indent=0)
def SetFromWxDateTime(self, wxdt): def SetWxDateTime(self, wxdt):
value = '%2d:%.2d:%.2d' % (wxdt.GetHour(), wxdt.GetMinute(), wxdt.GetSecond()) value = '%2d:%.2d:%.2d' % (wxdt.GetHour(), wxdt.GetMinute(), wxdt.GetSecond())
self.SetValue(value) self.SetValue(value)
@@ -236,18 +258,6 @@ class wxTimeCtrl(wxTextCtrl):
t = DateTime.Time(self.__hour, self.__minute, self.__second) t = DateTime.Time(self.__hour, self.__minute, self.__second)
return t 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: # 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. Sets state variables that indicate the current cell and position within the control.
""" """
self.__posCurrent = pos 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): def SetInsertionPoint(self, pos):
""" """
@@ -266,6 +277,54 @@ class wxTimeCtrl(wxTextCtrl):
wxTextCtrl.SetInsertionPoint(self, pos) # (causes EVT_TEXT event to fire) 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): def __OnTextChange(self, event):
""" """
This private event handler is required to retain the current position information of the cursor This private event handler is required to retain the current position information of the cursor
@@ -284,17 +343,11 @@ class wxTimeCtrl(wxTextCtrl):
event.Skip() event.Skip()
_dbg(indent=0) _dbg(indent=0)
def __OnFocus(self, event): def __OnSpin(self, key):
""" self.IncrementValue(key, self.__posCurrent)
This internal event handler ensures legal setting of input cursor on (re)focus to the control. self.SetFocus()
"""
_dbg('wxTimeCtrl::OnFocus; ctrl id=', event.GetId())
self.__SetCurrentCell(0)
self.__bSelection = false
self.__posSelectTo = self.__posCurrent
self.SetInsertionPoint(self.__posCurrent) self.SetInsertionPoint(self.__posCurrent)
event.Skip() self.SetSelection(self.__posCurrent, self.__posSelectTo)
def __OnSpinUp(self, event): def __OnSpinUp(self, event):
""" """
@@ -302,9 +355,8 @@ class wxTimeCtrl(wxTextCtrl):
causes control to behave as if up arrow was pressed. causes control to behave as if up arrow was pressed.
""" """
_dbg('wxTimeCtrl::OnSpinUp') _dbg('wxTimeCtrl::OnSpinUp')
pos = self.GetInsertionPoint() self.__OnSpin(WXK_UP)
self.IncrementValue(WXK_UP, pos)
self.SetInsertionPoint(pos)
def __OnSpinDown(self, event): def __OnSpinDown(self, event):
""" """
@@ -312,9 +364,42 @@ class wxTimeCtrl(wxTextCtrl):
causes control to behave as if down arrow was pressed. causes control to behave as if down arrow was pressed.
""" """
_dbg('wxTimeCtrl::OnSpinDown') _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() pos = self.GetInsertionPoint()
self.IncrementValue(WXK_DOWN, pos) self.__SetCurrentCell(pos)
self.SetInsertionPoint(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): def __OnChar(self, event):
@@ -339,7 +424,7 @@ class wxTimeCtrl(wxTextCtrl):
sel_start, sel_to = self.GetSelection() sel_start, sel_to = self.GetSelection()
selection = sel_start != sel_to selection = sel_start != sel_to
if not selection: if not selection:
self.__bSelection = false # predict unselection of entire region self.__bSelection = false # predict unselection of entire region
_dbg('keycode = ', key) _dbg('keycode = ', key)
_dbg('pos = ', pos) _dbg('pos = ', pos)
@@ -350,8 +435,8 @@ class wxTimeCtrl(wxTextCtrl):
elif key == WXK_TAB: # skip to next field if applicable: elif key == WXK_TAB: # skip to next field if applicable:
_dbg('key == WXK_TAB') _dbg('key == WXK_TAB')
dict_range = self.__dictCellRange dict_range = _dictCellRange # (for brevity)
dict_start = self.__dictStartCellPos dict_start = _dictStartCellPos
if event.ShiftDown(): # tabbing backwords if event.ShiftDown(): # tabbing backwords
###(NOTE: doesn't work; wxTE_PROCESS_TAB doesn't appear to send us this event!) ###(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 != pos:
if sel_to - 1 == pos: # allow unselection of position if sel_to - 1 == pos: # allow unselection of position
self.__bSelection = false # predict unselection of entire region self.__bSelection = false # predict unselection of entire region
self.__posSelectTo = pos
event.Skip() event.Skip()
if pos in self.__listStartCellPos: # can't select pass delimiters if pos in _listStartCellPos: # can't select pass delimiters
_dbg(indent=0) _dbg(indent=0)
return 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 # at delimeter
_dbg('set selection from', pos-1, 'to', self.__posCurrent) _dbg('set selection from', pos-1, 'to', self.__posCurrent)
self.__bSelection = true
self.__posSelectTo = pos
self.__posCurrent = pos-1 self.__posCurrent = pos-1
self.SetSelection(self.__posCurrent, self.__posSelectTo) self.SetSelection(self.__posCurrent, pos)
_dbg(indent=0) _dbg(indent=0)
return return
else: event.Skip() # allow selection else:
self.__posSelectTo = sel_to - 1 # predict change in selection
event.Skip() # allow selection
# else... not selecting # else... not selecting
if selection: if selection:
_dbg('sel_start=', sel_start, 'sel_to=', sel_to, '(Clearing selection)') _dbg('sel_start=', sel_start, 'sel_to=', sel_to, '(Clearing selection)')
self.SetSelection(pos,pos) # clear selection self.SetSelection(pos,pos) # clear selection
self.__bSelection = false
if pos == 0: # let base ctrl handle left bound case if pos == 0: # let base ctrl handle left bound case
event.Skip() 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.SetInsertionPoint(pos-1) # (this causes a EVT_TEXT)
self.__SetCurrentCell(pos-2) # set resulting position as "current" self.__SetCurrentCell(pos-2) # set resulting position as "current"
else: else:
@@ -433,15 +518,13 @@ class wxTimeCtrl(wxTextCtrl):
_dbg('key == WXK_RIGHT') _dbg('key == WXK_RIGHT')
if event.ShiftDown(): if event.ShiftDown():
_dbg('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) _dbg(indent=0)
return 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 # at delimeter
_dbg('set selection from', self.__posCurrent, 'to', pos+1) _dbg('set selection from', self.__posCurrent, 'to', pos+1)
self.__bSelection = true self.SetSelection(self.__posCurrent, pos+1)
self.__posSelectTo = pos+1
self.SetSelection(self.__posCurrent, self.__posSelectTo)
_dbg(indent=0) _dbg(indent=0)
return return
else: event.Skip() else: event.Skip()
@@ -451,26 +534,31 @@ class wxTimeCtrl(wxTextCtrl):
_dbg('sel_start=', sel_start, 'sel_to=', sel_to, '(Clearing selection)') _dbg('sel_start=', sel_start, 'sel_to=', sel_to, '(Clearing selection)')
pos = sel_start pos = sel_start
self.SetSelection(pos,pos) # clear selection self.SetSelection(pos,pos) # clear selection
self.__bSelection = false if pos == _dictStartCellPos[self.__lastCell]+1:
if pos == self.__dictStartCellPos[self.__lastCell]+1:
_dbg(indent=0) _dbg(indent=0)
return # don't allow cursor past last cell 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.SetInsertionPoint(pos+1) # (this causes a EVT_TEXT)
self.__SetCurrentCell(pos+2) # set resulting position self.__SetCurrentCell(pos+2) # set resulting position
else: else:
self.__SetCurrentCell(pos+1) # record the new cell position after the event is finished self.__SetCurrentCell(pos+1) # record the new cell position after the event is finished
self.__bSelection = false
event.Skip() event.Skip()
elif key in (WXK_UP, WXK_DOWN): elif key in (WXK_UP, WXK_DOWN):
_dbg('key in (WXK_UP, WXK_DOWN)') _dbg('key in (WXK_UP, WXK_DOWN)')
self.IncrementValue(key, pos) # increment/decrement as appropriate self.IncrementValue(key, pos) # increment/decrement as appropriate
self.SetInsertionPoint(pos)
elif key < WXK_SPACE or key == WXK_DELETE or key > 255: elif key < WXK_SPACE or key == WXK_DELETE or key > 255:
event.Skip() # non alphanumeric; process normally (Right thing to do?) 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 elif chr(key) in string.digits: # let ChangeValue validate and update current position
self.ChangeValue(chr(key), pos) # handle event (and swallow it) self.ChangeValue(chr(key), pos) # handle event (and swallow it)
@@ -482,6 +570,7 @@ class wxTimeCtrl(wxTextCtrl):
return return
_dbg(indent=0) _dbg(indent=0)
def IncrementValue(self, key, pos): def IncrementValue(self, key, pos):
_dbg('wxTimeCtrl::IncrementValue', key, pos) _dbg('wxTimeCtrl::IncrementValue', key, pos)
text = self.GetValue() text = self.GetValue()
@@ -490,15 +579,16 @@ class wxTimeCtrl(wxTextCtrl):
selection = sel_start != sel_to selection = sel_start != sel_to
cell_selected = selection and sel_to -1 != pos 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: # Determine whether we should change the entire cell or just a portion of it:
if( not selection if( cell_selected
or cell_selected or (pos in _listStartCellPos and not selection)
or text[pos] == ' ' 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] == '9' and text[pos-1] == ' ' and key == WXK_UP)
or text[pos] == '1' and text[pos-1] == ' ' and key == WXK_DOWN or (text[pos] == '1' and text[pos-1] == ' ' and key == WXK_DOWN)
or pos >= dict_start['am_pm']): or pos >= dict_start['am_pm']):
_dbg(indent=1) _dbg(indent=1)
self.IncrementCell(key, pos) self.IncrementCell(key, pos)
_dbg(indent=0) _dbg(indent=0)
@@ -511,8 +601,8 @@ class wxTimeCtrl(wxTextCtrl):
else: digit = ' ' else: digit = ' '
else: else:
if pos == dict_start['hour']: if pos == dict_start['hour']:
if int(text[pos + 1]) >3: mod = 2 # allow for 20-23 if int(text[pos + 1]) >3: mod = 2 # allow for 20-23
else: mod = 3 # allow 00-19 else: mod = 3 # allow 00-19
elif pos == dict_start['hour'] + 1: elif pos == dict_start['hour'] + 1:
if self.__fmt24hr: if self.__fmt24hr:
if text[pos - 1] == '2': mod = 4 # allow hours 20-23 if text[pos - 1] == '2': mod = 4 # allow hours 20-23
@@ -533,13 +623,12 @@ class wxTimeCtrl(wxTextCtrl):
_dbg(indent=0) _dbg(indent=0)
def IncrementCell(self, key, pos): def IncrementCell(self, key, pos):
_dbg('wxTimeCtrl::IncrementCell', key, pos) _dbg('wxTimeCtrl::IncrementCell', key, pos)
self.__SetCurrentCell(pos) # determine current cell self.__SetCurrentCell(pos) # determine current cell
hour, minute, second = self.__hour, self.__minute, self.__second hour, minute, second = self.__hour, self.__minute, self.__second
text = self.GetValue() text = self.GetValue()
dict_start = self.__dictStartCellPos dict_start = _dictStartCellPos # (for brevity)
if key == WXK_UP: inc = 1 if key == WXK_UP: inc = 1
else: inc = -1 else: inc = -1
@@ -558,11 +647,10 @@ class wxTimeCtrl(wxTextCtrl):
newvalue = '%.2d:%.2d:%.2d' % (hour, minute, second) newvalue = '%.2d:%.2d:%.2d' % (hour, minute, second)
self.__posCurrent = self.__cellStart self.__posCurrent = self.__cellStart
self.__posSelectTo = self.__cellEnd
self.__bSelection = true
_dbg(indent=1) _dbg(indent=1)
self.SetValue(newvalue) self.SetValue(newvalue)
_dbg(indent=0) _dbg(indent=0)
self.SetSelection(self.__cellStart, self.__cellEnd)
def ChangeValue(self, char, pos): def ChangeValue(self, char, pos):
@@ -574,10 +662,11 @@ class wxTimeCtrl(wxTextCtrl):
self.__posSelectTo = sel_to self.__posSelectTo = sel_to
self.__bSelection = selection = sel_start != sel_to self.__bSelection = selection = sel_start != sel_to
cell_selected = selection and sel_to -1 != pos 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): elif( 0 < pos < dict_start['am_pm'] and char not in string.digits):
return # AM/PM not allowed in this position return # AM/PM not allowed in this position
@@ -588,25 +677,28 @@ class wxTimeCtrl(wxTextCtrl):
if pos == hour_start: # if at 1st position, if pos == hour_start: # if at 1st position,
if self.__fmt24hr: # and using 24 hour format if self.__fmt24hr: # and using 24 hour format
if char not in ('0', '1', '2'): # return if digit not 0,1, or 2 if cell_selected: # replace cell contents with hour represented by digit
return
if cell_selected: # replace cell contents
newtext = '%.2d' % int(char) + text[hour_start+2:] 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 else: # relace current position
newtext = char + text[pos+1:] newtext = char + text[pos+1:]
else: # (12 hour format) else: # (12 hour format)
if char not in ('1', ' '): # can only type a 1 or space if cell_selected:
return if char == ' ': return # can't erase entire cell
if text[pos+1] not in ('0', '1', '2'): # and then, only if other column is 0,1, or 2 elif char == '0': # treat 0 as '12'
return newtext = '12' + text[hour_start+2:]
if( char == ' ' # and char isn't space and else: # replace cell contents with hour represented by digit
and (cell_selected or text[pos+1] == '0')): # 2nd column is 0 or cell isn't selected newtext = '%2d' % int(char) + text[hour_start+2:]
return else:
# else... ok if char not in ('1', ' '): # can only type a 1 or space
if cell_selected: # replace cell contents return
newtext = '%2d' % int(char) + text[hour_start+2:] if text[pos+1] not in ('0', '1', '2'): # and then, only if other column is 0,1, or 2
else: # relace current position return
newtext = char + text[pos+1:] 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 if char == ' ': self.SetInsertionPoint(pos+1) # move insert point to legal position
elif pos == hour_start+1: # if editing 2nd position of hour elif pos == hour_start+1: # if editing 2nd position of hour
@@ -634,16 +726,17 @@ class wxTimeCtrl(wxTextCtrl):
else: return # not a valid position else: return # not a valid position
# update member position vars and set selection to character changed # update member position vars and set selection to character changed
self.__posCurrent = pos+1 if not cell_selected:
self.__SetCurrentCell(self.__posCurrent) _dbg('selecting current digit')
self.SetSelection(self.__posCurrent, pos+1)
_dbg(indent=1) _dbg(indent=1)
_dbg('newtext=', newtext) _dbg('newtext=', newtext)
_dbg(indent=0) _dbg(indent=0)
self.SetValue(newtext) self.SetValue(newtext)
self.SetInsertionPoint(pos+1) self.SetSelection(self.__posCurrent, self.__posSelectTo)
#---------------------------------------------------------------------------- #----------------------------------------------------------------------------
# Test jig for wxTimeCtrl:
if __name__ == '__main__': if __name__ == '__main__':
import traceback import traceback
@@ -654,14 +747,16 @@ if __name__ == '__main__':
fmt24hr = 0, test_mx = 0, fmt24hr = 0, test_mx = 0,
style = wxTAB_TRAVERSAL ): style = wxTAB_TRAVERSAL ):
self.test_mx = test_mx
wxPanel.__init__(self, parent, id, pos, size, style) wxPanel.__init__(self, parent, id, pos, size, style)
sizer = wxBoxSizer( wxHORIZONTAL ) self.test_mx = test_mx
self.tc = wxTimeCtrl(self, 10, fmt24hr = fmt24hr) 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 ) sb = wxSpinButton( self, 20, wxDefaultPosition, wxSize(-1,20), 0 )
self.tc.BindSpinButton(sb) 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 ) sizer.AddWindow( sb, 0, wxALIGN_CENTRE|wxRIGHT|wxTOP|wxBOTTOM, 5 )
self.SetAutoLayout( true ) self.SetAutoLayout( true )
@@ -686,7 +781,7 @@ if __name__ == '__main__':
fmt24hr = '24' in sys.argv fmt24hr = '24' in sys.argv
test_mx = 'mx' in sys.argv test_mx = 'mx' in sys.argv
try: 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) panel = TestPanel(frame, -1, wxPoint(-1,-1), fmt24hr=fmt24hr, test_mx = test_mx)
frame.Show(true) frame.Show(true)
except: except: