Added wxTimeCtrl from Will Sadkin.
git-svn-id: https://svn.wxwidgets.org/svn/wx/wxWidgets/branches/WX_2_4_BRANCH@17267 c3d73ce0-8a6f-49c7-b76d-6d57e0e08775
This commit is contained in:
@@ -176,7 +176,7 @@ example.
|
|||||||
|
|
||||||
Added wxPython.lib.mixins.rubberband module from Robb Shecter.
|
Added wxPython.lib.mixins.rubberband module from Robb Shecter.
|
||||||
|
|
||||||
|
Added wxTimeCtrl from Will Sadkin.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@@ -37,6 +37,7 @@ _treeList = [
|
|||||||
'wxKeyEvents',
|
'wxKeyEvents',
|
||||||
'wxWizard',
|
'wxWizard',
|
||||||
'wxXmlResourceHandler',
|
'wxXmlResourceHandler',
|
||||||
|
'wxTimeCtrl',
|
||||||
]),
|
]),
|
||||||
|
|
||||||
# managed windows == things with a caption you can close
|
# managed windows == things with a caption you can close
|
||||||
@@ -129,6 +130,7 @@ _treeList = [
|
|||||||
'wxRightTextCtrl',
|
'wxRightTextCtrl',
|
||||||
'wxStyledTextCtrl_1',
|
'wxStyledTextCtrl_1',
|
||||||
'wxStyledTextCtrl_2',
|
'wxStyledTextCtrl_2',
|
||||||
|
'wxTimeCtrl',
|
||||||
]),
|
]),
|
||||||
|
|
||||||
# How to lay out the controls in a frame/dialog
|
# How to lay out the controls in a frame/dialog
|
||||||
|
117
wxPython/demo/wxTimeCtrl.py
Normal file
117
wxPython/demo/wxTimeCtrl.py
Normal file
@@ -0,0 +1,117 @@
|
|||||||
|
from wxPython.wx import *
|
||||||
|
from wxPython.lib.timectrl import *
|
||||||
|
|
||||||
|
import string
|
||||||
|
|
||||||
|
#----------------------------------------------------------------------
|
||||||
|
|
||||||
|
class TestPanel(wxPanel):
|
||||||
|
def __init__(self, parent, log):
|
||||||
|
wxPanel.__init__(self, parent, -1)
|
||||||
|
self.log = log
|
||||||
|
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 )
|
||||||
|
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 )
|
||||||
|
|
||||||
|
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 )
|
||||||
|
|
||||||
|
hsizer2 = wxBoxSizer( wxHORIZONTAL )
|
||||||
|
self.time24 = wxTimeCtrl(panel, 50, fmt24hr=true, name="24 hour time")
|
||||||
|
hsizer2.AddWindow( self.time24, 0, wxALIGN_CENTRE, 5 )
|
||||||
|
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 )
|
||||||
|
|
||||||
|
panel.SetAutoLayout(true)
|
||||||
|
panel.SetSizer(grid)
|
||||||
|
grid.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)
|
||||||
|
|
||||||
|
|
||||||
|
def OnTimeChange(self, event):
|
||||||
|
timectrl = self.panel.FindWindowById(event.GetId())
|
||||||
|
self.log.write('%s = %s\n' % (
|
||||||
|
timectrl.GetName(), timectrl.GetValue() ) )
|
||||||
|
|
||||||
|
|
||||||
|
#----------------------------------------------------------------------
|
||||||
|
|
||||||
|
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 = """<html><body>
|
||||||
|
<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.
|
||||||
|
<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.
|
||||||
|
<P>
|
||||||
|
Here's the interface for wxTimeCtrl:
|
||||||
|
<DL>
|
||||||
|
|
||||||
|
<PRE>
|
||||||
|
<B>wxTimeCtrl</B>(parent, id = -1,
|
||||||
|
<B>value</B> = '12:00:00 AM',
|
||||||
|
pos = wxDefaultPosition,
|
||||||
|
size = wxDefaultSize,
|
||||||
|
<B>fmt24hr</B> = false,
|
||||||
|
<B>spinButton</B> = None,
|
||||||
|
<B>style</B> = wxTE_PROCESS_TAB,
|
||||||
|
name = "time")
|
||||||
|
</PRE>
|
||||||
|
<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.)
|
||||||
|
<DL><B>size</B><DD>The size of the control will be automatically adjusted for 12/24 hour format if wxDefaultSize is specified.
|
||||||
|
<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.
|
||||||
|
<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.)
|
||||||
|
<DT><B>style</B><DD>By default, wxTimeCtrl will process TAB events, by allowing tab to the different cells within the control.
|
||||||
|
</DL>
|
||||||
|
</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>
|
||||||
|
<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>
|
||||||
|
<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>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>
|
||||||
|
</body></html>
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
import sys,os
|
||||||
|
import run
|
||||||
|
run.main(['', os.path.basename(sys.argv[0])])
|
||||||
|
|
||||||
|
|
||||||
|
|
697
wxPython/wxPython/lib/timectrl.py
Normal file
697
wxPython/wxPython/lib/timectrl.py
Normal file
@@ -0,0 +1,697 @@
|
|||||||
|
#----------------------------------------------------------------------------
|
||||||
|
# Name: wxTimeCtrl.py
|
||||||
|
# Author: Will Sadkin
|
||||||
|
# Created: 09/19/2002
|
||||||
|
# Copyright: (c) 2002 by Will Sadkin, 2002
|
||||||
|
# License: wxWindows license
|
||||||
|
#----------------------------------------------------------------------------
|
||||||
|
# NOTE:
|
||||||
|
# This was written way it is because of the lack of masked edit controls
|
||||||
|
# in wxWindows/wxPython. I would also have preferred to derive this
|
||||||
|
# control from a wxSpinCtrl rather than wxTextCtrl, but the wxTextCtrl
|
||||||
|
# component of that control is inaccessible through the interface exposed in
|
||||||
|
# wxPython.
|
||||||
|
#
|
||||||
|
# wxTimeCtrl does not use validators, because it does careful manipulation
|
||||||
|
# of the cursor in the text window on each keystroke, and validation is
|
||||||
|
# cursor-position specific, so the control intercepts the key codes before the
|
||||||
|
# validator would fire.
|
||||||
|
#
|
||||||
|
|
||||||
|
from wxPython.wx import *
|
||||||
|
import string
|
||||||
|
|
||||||
|
# The following bit of function is for debugging the subsequent code.
|
||||||
|
# To turn on debugging output, set _debug to 1
|
||||||
|
_debug = 0
|
||||||
|
_indent = 0
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# This class of event fires whenever the value of the time changes in the control:
|
||||||
|
wxEVT_TIMEVAL_UPDATED = wxNewId()
|
||||||
|
def EVT_TIMEUPDATE(win, id, func):
|
||||||
|
"""Used to trap events indicating that the current time has been changed."""
|
||||||
|
win.Connect(id, -1, wxEVT_TIMEVAL_UPDATED, func)
|
||||||
|
|
||||||
|
class TimeUpdatedEvent(wxPyCommandEvent):
|
||||||
|
def __init__(self, id, value ='12:00:00 AM'):
|
||||||
|
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"""
|
||||||
|
return self.value
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class wxTimeCtrl(wxTextCtrl):
|
||||||
|
def __init__ (
|
||||||
|
self, parent, id=-1, value = '12:00:00 AM',
|
||||||
|
pos = wxDefaultPosition, size = wxDefaultSize,
|
||||||
|
fmt24hr=0,
|
||||||
|
spinButton = None,
|
||||||
|
style = wxTE_PROCESS_TAB, name = "time"
|
||||||
|
):
|
||||||
|
self.__fmt24hr = fmt24hr
|
||||||
|
if size == wxDefaultSize:
|
||||||
|
# set appropriate default sizes depending on format:
|
||||||
|
if self.__fmt24hr: size = wxSize(52, -1)
|
||||||
|
else: size = wxSize(70, -1)
|
||||||
|
|
||||||
|
wxTextCtrl.__init__(self, parent, id, value='',
|
||||||
|
pos=pos, size=size, style=style, name=name)
|
||||||
|
|
||||||
|
|
||||||
|
# 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'
|
||||||
|
|
||||||
|
# Validate initial value and set if appropriate
|
||||||
|
try:
|
||||||
|
self.SetValue(value)
|
||||||
|
except ValueError:
|
||||||
|
self.SetValue('12:00:00 AM')
|
||||||
|
|
||||||
|
# set initial position and selection state
|
||||||
|
self.__SetCurrentCell(self.__dictStartCellPos['hour'])
|
||||||
|
self.__bSelection = false
|
||||||
|
|
||||||
|
EVT_TEXT(self, self.GetId(), self.__OnTextChange)
|
||||||
|
EVT_SET_FOCUS(self, self.__OnFocus)
|
||||||
|
EVT_CHAR(self, self.__OnChar)
|
||||||
|
|
||||||
|
if spinButton:
|
||||||
|
self.BindSpinbutton(spinButton) # bind spin button up/down events to this control
|
||||||
|
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
fmt12len = dict_range['am_pm'][-1]
|
||||||
|
fmt24len = dict_range['second'][-1]
|
||||||
|
try:
|
||||||
|
separators_correct = value[2] == ':' and value[5] == ':'
|
||||||
|
len_ok = len(value) in (fmt12len, fmt24len)
|
||||||
|
|
||||||
|
if len(value) > fmt24len:
|
||||||
|
separators_correct = separators_correct and value[8] == ' '
|
||||||
|
hour = int(value[dict_range['hour'][0]:dict_range['hour'][-1]])
|
||||||
|
hour_ok = ((hour in range(0,24) and len(value) == fmt24len)
|
||||||
|
or (hour in range(1,13) and len(value) == fmt12len
|
||||||
|
and value[dict_start['am_pm']:] in ('AM', 'PM')))
|
||||||
|
|
||||||
|
minute = int(value[dict_range['minute'][0]:dict_range['minute'][-1]])
|
||||||
|
min_ok = minute in range(60)
|
||||||
|
second = int(value[dict_range['second'][0]:dict_range['second'][-1]])
|
||||||
|
sec_ok = second in range(60)
|
||||||
|
|
||||||
|
_dbg('len_ok =', len_ok, 'separators_correct =', separators_correct)
|
||||||
|
_dbg('hour =', hour, 'hour_ok =', hour_ok, 'min_ok =', min_ok, 'sec_ok =', sec_ok)
|
||||||
|
|
||||||
|
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'
|
||||||
|
if hour != 12 and not am:
|
||||||
|
self.__hour = hour = (hour+12) % 24
|
||||||
|
elif hour == 12:
|
||||||
|
if am: self.__hour = hour = 0
|
||||||
|
|
||||||
|
self.__minute = minute
|
||||||
|
self.__second = second
|
||||||
|
|
||||||
|
# valid time
|
||||||
|
need_to_convert = ((self.__fmt24hr and len(value) == fmt12len)
|
||||||
|
or (not self.__fmt24hr and len(value) == fmt24len))
|
||||||
|
_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:
|
||||||
|
if hour > 12:
|
||||||
|
hour = hour - 12
|
||||||
|
am_pm = 'PM'
|
||||||
|
elif hour == 12:
|
||||||
|
am_pm = 'PM'
|
||||||
|
else:
|
||||||
|
if hour == 0: hour = 12
|
||||||
|
am_pm = 'AM'
|
||||||
|
text = '%2d:%.2d:%.2d %s' % (hour, minute, second, am_pm)
|
||||||
|
else:
|
||||||
|
text = value
|
||||||
|
_dbg('text=', text)
|
||||||
|
wxTextCtrl.SetValue(self, text)
|
||||||
|
_dbg('firing TimeUpdatedEvent...')
|
||||||
|
self.GetEventHandler().ProcessEvent(TimeUpdatedEvent(self.GetId(), text))
|
||||||
|
else:
|
||||||
|
_dbg('len_ok:', len_ok, 'separators_correct =', separators_correct)
|
||||||
|
_dbg('hour_ok:', hour_ok, 'min_ok:', min_ok, 'sec_ok:', sec_ok, indent=0)
|
||||||
|
raise ValueError, 'value is not a valid time string'
|
||||||
|
|
||||||
|
except (TypeError, ValueError):
|
||||||
|
_dbg(indent=0)
|
||||||
|
raise ValueError, 'value is not a valid time string'
|
||||||
|
_dbg(indent=0)
|
||||||
|
|
||||||
|
def SetFromWxDateTime(self, wxdt):
|
||||||
|
value = '%2d:%.2d:%.2d' % (wxdt.GetHour(), wxdt.GetMinute(), wxdt.GetSecond())
|
||||||
|
self.SetValue(value)
|
||||||
|
|
||||||
|
def GetWxDateTime(self):
|
||||||
|
t = wxDateTimeFromHMS(self.__hour, self.__minute, self.__second)
|
||||||
|
return t
|
||||||
|
|
||||||
|
def SetMxDateTime(self, mxdt):
|
||||||
|
from mx import DateTime
|
||||||
|
value = '%2d:%.2d:%.2d' % (mxdt.hour, mxdt.minute, mxdt.second)
|
||||||
|
self.SetValue(value)
|
||||||
|
|
||||||
|
def GetMxDateTime(self):
|
||||||
|
from mx import DateTime
|
||||||
|
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:
|
||||||
|
|
||||||
|
def __SetCurrentCell(self, pos):
|
||||||
|
"""
|
||||||
|
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]
|
||||||
|
|
||||||
|
def SetInsertionPoint(self, pos):
|
||||||
|
"""
|
||||||
|
Records the specified position and associated cell before calling base class' function.
|
||||||
|
"""
|
||||||
|
self.__SetCurrentCell(pos)
|
||||||
|
wxTextCtrl.SetInsertionPoint(self, pos) # (causes EVT_TEXT event to fire)
|
||||||
|
|
||||||
|
|
||||||
|
def __OnTextChange(self, event):
|
||||||
|
"""
|
||||||
|
This private event handler is required to retain the current position information of the cursor
|
||||||
|
after update to the underlying text control is done.
|
||||||
|
"""
|
||||||
|
_dbg('wxTimeCtrl::OnTextChange', indent=1)
|
||||||
|
self.__SetCurrentCell(self.__posCurrent) # ensure cell range vars are set
|
||||||
|
|
||||||
|
# Note: must call self.SetSelection here to preserve insertion point cursor after update!
|
||||||
|
# (I don't know why, but this does the trick!)
|
||||||
|
if self.__bSelection:
|
||||||
|
_dbg('reselecting from ', self.__posCurrent, 'to', self.__posSelectTo)
|
||||||
|
self.SetSelection(self.__posCurrent, self.__posSelectTo)
|
||||||
|
else:
|
||||||
|
self.SetSelection(self.__posCurrent, self.__posCurrent)
|
||||||
|
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
|
||||||
|
self.SetInsertionPoint(self.__posCurrent)
|
||||||
|
event.Skip()
|
||||||
|
|
||||||
|
|
||||||
|
def __OnSpinUp(self, event):
|
||||||
|
"""
|
||||||
|
Event handler for any bound spin button on EVT_SPIN_UP;
|
||||||
|
causes control to behave as if up arrow was pressed.
|
||||||
|
"""
|
||||||
|
_dbg('wxTimeCtrl::OnSpinUp')
|
||||||
|
pos = self.GetInsertionPoint()
|
||||||
|
self.IncrementValue(WXK_UP, pos)
|
||||||
|
self.SetInsertionPoint(pos)
|
||||||
|
|
||||||
|
def __OnSpinDown(self, event):
|
||||||
|
"""
|
||||||
|
Event handler for any bound spin button on EVT_SPIN_DOWN;
|
||||||
|
causes control to behave as if down arrow was pressed.
|
||||||
|
"""
|
||||||
|
_dbg('wxTimeCtrl::OnSpinDown')
|
||||||
|
pos = self.GetInsertionPoint()
|
||||||
|
self.IncrementValue(WXK_DOWN, pos)
|
||||||
|
self.SetInsertionPoint(pos)
|
||||||
|
|
||||||
|
|
||||||
|
def __OnChar(self, event):
|
||||||
|
"""
|
||||||
|
This private event handler is the main control point for the wxTimeCtrl.
|
||||||
|
It governs whether input characters are accepted and if so, handles them
|
||||||
|
so as to provide appropriate cursor and selection behavior for the control.
|
||||||
|
"""
|
||||||
|
_dbg('wxTimeCtrl::OnChar', indent=1)
|
||||||
|
|
||||||
|
# NOTE: Returning without calling event.Skip() eats the event before it
|
||||||
|
# gets to the text control...
|
||||||
|
|
||||||
|
key = event.GetKeyCode()
|
||||||
|
text = self.GetValue()
|
||||||
|
pos = self.GetInsertionPoint()
|
||||||
|
if pos != self.__posCurrent:
|
||||||
|
_dbg("insertion point has moved; resetting current cell")
|
||||||
|
self.__SetCurrentCell(pos)
|
||||||
|
self.SetSelection(self.__posCurrent, self.__posCurrent)
|
||||||
|
|
||||||
|
sel_start, sel_to = self.GetSelection()
|
||||||
|
selection = sel_start != sel_to
|
||||||
|
if not selection:
|
||||||
|
self.__bSelection = false # predict unselection of entire region
|
||||||
|
|
||||||
|
_dbg('keycode = ', key)
|
||||||
|
_dbg('pos = ', pos)
|
||||||
|
|
||||||
|
if key in (WXK_DELETE, WXK_BACK): # don't allow deletion
|
||||||
|
_dbg(indent=0)
|
||||||
|
return
|
||||||
|
|
||||||
|
elif key == WXK_TAB: # skip to next field if applicable:
|
||||||
|
_dbg('key == WXK_TAB')
|
||||||
|
dict_range = self.__dictCellRange
|
||||||
|
dict_start = self.__dictStartCellPos
|
||||||
|
if event.ShiftDown(): # tabbing backwords
|
||||||
|
|
||||||
|
###(NOTE: doesn't work; wxTE_PROCESS_TAB doesn't appear to send us this event!)
|
||||||
|
|
||||||
|
_dbg('event.ShiftDown()')
|
||||||
|
if pos in dict_range['hour']: # already in 1st field
|
||||||
|
self.__SetCurrentCell(dict_start['hour']) # ensure we have our member vars set
|
||||||
|
event.Skip() #then do normal tab processing for the form
|
||||||
|
|
||||||
|
elif pos in dict_range['minute']: # skip to hours field
|
||||||
|
new_pos = dict_start['hour']
|
||||||
|
elif pos in dict_range['second']: # skip to minutes field
|
||||||
|
new_pos = dict_start['minute']
|
||||||
|
elif pos in dict_range['am_pm']: # skip to seconds field
|
||||||
|
new_pos = dict_start['second']
|
||||||
|
|
||||||
|
self.__bSelection = true
|
||||||
|
self.__posSelectTo = new_pos+2
|
||||||
|
self.SetInsertionPoint(new_pos) # force insert point to jump to next cell (swallowing TAB)
|
||||||
|
|
||||||
|
else:
|
||||||
|
if pos in dict_range[self.__lastCell]: # already in last field; ensure we have our members set
|
||||||
|
self.__SetCurrentCell(dict_start[self.__lastCell])
|
||||||
|
_dbg('tab in last cell')
|
||||||
|
event.Skip() # then do normal tab processing for the form
|
||||||
|
_dbg(indent=0)
|
||||||
|
return
|
||||||
|
|
||||||
|
if pos in dict_range['second']: # skip to AM/PM field (if not last cell)
|
||||||
|
new_pos = dict_start['am_pm']
|
||||||
|
elif pos in dict_range['minute']: # skip to seconds field
|
||||||
|
new_pos = dict_start['second']
|
||||||
|
elif pos in dict_range['hour']: # skip to minutes field
|
||||||
|
new_pos = dict_start['minute']
|
||||||
|
|
||||||
|
self.__bSelection = true
|
||||||
|
self.__posSelectTo = new_pos+2
|
||||||
|
self.SetInsertionPoint(new_pos) # force insert point to jump to next cell (swallowing TAB)
|
||||||
|
|
||||||
|
elif key == WXK_LEFT: # move left; set insertion point as appropriate:
|
||||||
|
_dbg('key == WXK_LEFT')
|
||||||
|
if event.ShiftDown(): # selecting a range...
|
||||||
|
_dbg('event.ShiftDown()')
|
||||||
|
if sel_to != pos:
|
||||||
|
if sel_to - 1 == pos: # allow unselection of position
|
||||||
|
self.__bSelection = false # predict unselection of entire region
|
||||||
|
event.Skip()
|
||||||
|
if pos in self.__listStartCellPos: # can't select pass delimiters
|
||||||
|
_dbg(indent=0)
|
||||||
|
return
|
||||||
|
elif pos in self.__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)
|
||||||
|
_dbg(indent=0)
|
||||||
|
return
|
||||||
|
else: 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:
|
||||||
|
self.SetInsertionPoint(pos-1) # (this causes a EVT_TEXT)
|
||||||
|
self.__SetCurrentCell(pos-2) # set resulting position as "current"
|
||||||
|
else:
|
||||||
|
self.__SetCurrentCell(pos-1) # record the new cell position after the event is finishedI
|
||||||
|
# and update spinbutton as necessary
|
||||||
|
event.Skip() # let base control handle event
|
||||||
|
|
||||||
|
elif key == WXK_RIGHT: # move right
|
||||||
|
_dbg('key == WXK_RIGHT')
|
||||||
|
if event.ShiftDown():
|
||||||
|
_dbg('event.ShiftDown()')
|
||||||
|
if sel_to in self.__listDelimPos: # can't select pass delimiters
|
||||||
|
_dbg(indent=0)
|
||||||
|
return
|
||||||
|
elif pos in self.__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)
|
||||||
|
_dbg(indent=0)
|
||||||
|
return
|
||||||
|
else: event.Skip()
|
||||||
|
|
||||||
|
else:
|
||||||
|
if selection:
|
||||||
|
_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:
|
||||||
|
_dbg(indent=0)
|
||||||
|
return # don't allow cursor past last cell
|
||||||
|
if pos in self.__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 string.digits: # let ChangeValue validate and update current position
|
||||||
|
self.ChangeValue(chr(key), pos) # handle event (and swallow it)
|
||||||
|
|
||||||
|
elif chr(key) in ('A', 'P', 'M', ' '): # let ChangeValue validate and update current position
|
||||||
|
self.ChangeValue(chr(key), pos) # handle event (and swallow it)
|
||||||
|
|
||||||
|
else: # disallowed char; swallow event
|
||||||
|
_dbg(indent=0)
|
||||||
|
return
|
||||||
|
_dbg(indent=0)
|
||||||
|
|
||||||
|
def IncrementValue(self, key, pos):
|
||||||
|
_dbg('wxTimeCtrl::IncrementValue', key, pos)
|
||||||
|
text = self.GetValue()
|
||||||
|
|
||||||
|
sel_start, sel_to = self.GetSelection()
|
||||||
|
selection = sel_start != sel_to
|
||||||
|
cell_selected = selection and sel_to -1 != pos
|
||||||
|
|
||||||
|
dict_start = self.__dictStartCellPos
|
||||||
|
|
||||||
|
# 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
|
||||||
|
or pos >= dict_start['am_pm']):
|
||||||
|
_dbg(indent=1)
|
||||||
|
self.IncrementCell(key, pos)
|
||||||
|
_dbg(indent=0)
|
||||||
|
else:
|
||||||
|
if key == WXK_UP: inc = 1
|
||||||
|
else: inc = -1
|
||||||
|
|
||||||
|
if pos == dict_start['hour'] and not self.__fmt24hr:
|
||||||
|
if text[pos] == ' ': digit = '1' # allow ' ' or 1 for 1st digit in 12hr format
|
||||||
|
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
|
||||||
|
elif pos == dict_start['hour'] + 1:
|
||||||
|
if self.__fmt24hr:
|
||||||
|
if text[pos - 1] == '2': mod = 4 # allow hours 20-23
|
||||||
|
else: mod = 10 # allow hours 00-19
|
||||||
|
else:
|
||||||
|
if text[pos - 1] == '1': mod = 3 # allow hours 10-12
|
||||||
|
else: mod = 10 # allow 0-9
|
||||||
|
|
||||||
|
elif pos in (dict_start['minute'],
|
||||||
|
dict_start['second']): mod = 6 # allow minutes/seconds 00-59
|
||||||
|
else: mod = 10
|
||||||
|
|
||||||
|
digit = '%d' % ((int(text[pos]) + inc) % mod)
|
||||||
|
|
||||||
|
_dbg(indent=1)
|
||||||
|
_dbg("new digit = \'%s\'" % digit)
|
||||||
|
self.ChangeValue(digit, pos)
|
||||||
|
_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
|
||||||
|
if key == WXK_UP: inc = 1
|
||||||
|
else: inc = -1
|
||||||
|
|
||||||
|
if self.__cellStart == dict_start['am_pm']:
|
||||||
|
am = text[dict_start['am_pm']:] == 'AM'
|
||||||
|
if am: hour = hour + 12
|
||||||
|
else: hour = hour - 12
|
||||||
|
else:
|
||||||
|
if self.__cellStart == dict_start['hour']:
|
||||||
|
hour = (hour + inc) % 24
|
||||||
|
elif self.__cellStart == dict_start['minute']:
|
||||||
|
minute = (minute + inc) % 60
|
||||||
|
elif self.__cellStart == dict_start['second']:
|
||||||
|
second = (second + inc) % 60
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
|
||||||
|
def ChangeValue(self, char, pos):
|
||||||
|
_dbg('wxTimeCtrl::ChangeValue', "\'" + char + "\'", pos)
|
||||||
|
text = self.GetValue()
|
||||||
|
|
||||||
|
self.__SetCurrentCell(pos)
|
||||||
|
sel_start, sel_to = self.GetSelection()
|
||||||
|
self.__posSelectTo = sel_to
|
||||||
|
self.__bSelection = selection = sel_start != sel_to
|
||||||
|
cell_selected = selection and sel_to -1 != pos
|
||||||
|
|
||||||
|
dict_start = self.__dictStartCellPos
|
||||||
|
|
||||||
|
if pos in self.__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
|
||||||
|
|
||||||
|
# See if we're changing the hour cell, and validate/update appropriately:
|
||||||
|
#
|
||||||
|
hour_start = dict_start['hour'] # (ie. 0)
|
||||||
|
|
||||||
|
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
|
||||||
|
newtext = '%.2d' % int(char) + text[hour_start+2:]
|
||||||
|
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 char == ' ': self.SetInsertionPoint(pos+1) # move insert point to legal position
|
||||||
|
|
||||||
|
elif pos == hour_start+1: # if editing 2nd position of hour
|
||||||
|
if( not self.__fmt24hr # and using 12 hour format
|
||||||
|
and text[hour_start] == '1' # if 1st char is 1,
|
||||||
|
and char not in ('0', '1', '2')): # disallow anything bug 0,1, or 2
|
||||||
|
return
|
||||||
|
newtext = text[hour_start] + char + text[hour_start+2:] # else any digit ok
|
||||||
|
|
||||||
|
# Do the same sort of validation for minute and second cells
|
||||||
|
elif pos in (dict_start['minute'], dict_start['second']):
|
||||||
|
if cell_selected: # if cell selected, replace value
|
||||||
|
newtext = text[:pos] + '%.2d' % int(char) + text[pos+2:]
|
||||||
|
elif int(char) > 5: return # else disallow > 59 for minute and second fields
|
||||||
|
else:
|
||||||
|
newtext = text[:pos] + char + text[pos+1:] # else ok
|
||||||
|
|
||||||
|
elif pos in (dict_start['minute']+1, dict_start['second']+1):
|
||||||
|
newtext = text[:pos] + char + text[pos+1:] # all digits ok for 2nd digit of minute/second
|
||||||
|
|
||||||
|
# Process AM/PM cell
|
||||||
|
elif pos == dict_start['am_pm']:
|
||||||
|
if char not in ('A','P'): return # disallow all but A or P as 1st char of column
|
||||||
|
newtext = text[:pos] + char + text[pos+1:]
|
||||||
|
else: return # not a valid position
|
||||||
|
|
||||||
|
# update member position vars and set selection to character changed
|
||||||
|
self.__posCurrent = pos+1
|
||||||
|
self.__SetCurrentCell(self.__posCurrent)
|
||||||
|
_dbg(indent=1)
|
||||||
|
_dbg('newtext=', newtext)
|
||||||
|
_dbg(indent=0)
|
||||||
|
self.SetValue(newtext)
|
||||||
|
self.SetInsertionPoint(pos+1)
|
||||||
|
|
||||||
|
|
||||||
|
class TestPanel(wxPanel):
|
||||||
|
def __init__(self, parent, id,
|
||||||
|
pos = wxPyDefaultPosition, size = wxPyDefaultSize,
|
||||||
|
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.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.AddWindow( sb, 0, wxALIGN_CENTRE|wxRIGHT|wxTOP|wxBOTTOM, 5 )
|
||||||
|
|
||||||
|
self.SetAutoLayout( true )
|
||||||
|
self.SetSizer( sizer )
|
||||||
|
sizer.Fit( self )
|
||||||
|
sizer.SetSizeHints( self )
|
||||||
|
|
||||||
|
EVT_TIMEUPDATE(self, self.tc.GetId(), self.OnTimeChange)
|
||||||
|
|
||||||
|
def OnTimeChange(self, event):
|
||||||
|
_dbg('OnTimeChange: value = ', event.GetValue())
|
||||||
|
wxdt = self.tc.GetWxDateTime()
|
||||||
|
_dbg('wxdt =', wxdt.GetHour(), wxdt.GetMinute(), wxdt.GetSecond())
|
||||||
|
if self.test_mx:
|
||||||
|
mxdt = self.tc.GetMxDateTime()
|
||||||
|
_dbg('mxdt =', mxdt.hour, mxdt.minute, mxdt.second)
|
||||||
|
|
||||||
|
|
||||||
|
#----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
import traceback
|
||||||
|
|
||||||
|
class MyApp(wxApp):
|
||||||
|
|
||||||
|
def OnInit(self):
|
||||||
|
import sys
|
||||||
|
fmt24hr = '24' in sys.argv
|
||||||
|
test_mx = 'mx' in sys.argv
|
||||||
|
|
||||||
|
try:
|
||||||
|
frame = wxFrame(NULL, -1, "Junk", wxPoint(20,20), wxSize(100,100) )
|
||||||
|
panel = TestPanel(frame, -1, wxPoint(-1,-1), fmt24hr=fmt24hr, test_mx = test_mx)
|
||||||
|
frame.Show(true)
|
||||||
|
except:
|
||||||
|
traceback.print_exc()
|
||||||
|
return false
|
||||||
|
|
||||||
|
return true
|
||||||
|
|
||||||
|
try:
|
||||||
|
app = MyApp(0)
|
||||||
|
app.MainLoop()
|
||||||
|
except:
|
||||||
|
traceback.print_exc()
|
Reference in New Issue
Block a user