+++ /dev/null
-#----------------------------------------------------------------------------
-# Name: timectrl.py
-# Author: Will Sadkin
-# Created: 09/19/2002
-# Copyright: (c) 2002 by Will Sadkin, 2002
-# RCS-ID: $Id$
-# 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.
-#
-# TimeCtrl 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.
-#
-# TimeCtrl now also supports .SetValue() with either strings or wxDateTime
-# values, as well as range limits, with the option of either enforcing them
-# or simply coloring the text of the control if the limits are exceeded.
-#
-# Note: this class now makes heavy use of wxDateTime for parsing and
-# regularization, but it always does so with ephemeral instances of
-# wxDateTime, as the C++/Python validity of these instances seems to not
-# persist. Because "today" can be a day for which an hour can "not exist"
-# or be counted twice (1 day each per year, for DST adjustments), the date
-# portion of all wxDateTimes used/returned have their date portion set to
-# Jan 1, 1970 (the "epoch.")
-#----------------------------------------------------------------------------
-# 12/13/2003 - Jeff Grimmett (grimmtooth@softhome.net)
-#
-# o Updated for V2.5 compatability
-# o wx.SpinCtl has some issues that cause the control to
-# lock up. Noted in other places using it too, it's not this module
-# that's at fault.
-#
-# 12/20/2003 - Jeff Grimmett (grimmtooth@softhome.net)
-#
-# o wxMaskedTextCtrl -> masked.TextCtrl
-# o wxTimeCtrl -> masked.TimeCtrl
-#
-
-"""
-*TimeCtrl* 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 TimeCtrl, and the up/down arrows act
-like a spin control. TimeCtrl also allows for an actual spin button to be attached
-to the control, so that it acts like the up/down arrow keys.
-
-The ! or c key sets the value of the control to the current time.
-
- Here's the API for TimeCtrl::
-
- from wx.lib.masked import TimeCtrl
-
- TimeCtrl(
- parent, id = -1,
- value = '00:00:00',
- pos = wx.DefaultPosition,
- size = wx.DefaultSize,
- style = wxTE_PROCESS_TAB,
- validator = wx.DefaultValidator,
- name = "time",
- format = 'HHMMSS',
- fmt24hr = False,
- displaySeconds = True,
- spinButton = None,
- min = None,
- max = None,
- limited = None,
- oob_color = "Yellow"
- )
-
-
- 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 wx.DefaultSize is specified. NOTE: due to a problem with wx.DateTime, if the
- locale does not use 'AM/PM' for its values, the default format will automatically
- change to 24 hour format, and an AttributeError will be thrown if a non-24 format
- is specified.
-
- style
- By default, TimeCtrl will process TAB events, by allowing tab to the
- different cells within the control.
-
- validator
- By default, TimeCtrl just uses the default (empty) validator, as all
- of its validation for entry control is handled internally. However, a validator
- can be supplied to provide data transfer capability to the control.
-
- format
- This parameter can be used instead of the fmt24hr and displaySeconds
- parameters, respectively; it provides a shorthand way to specify the time
- format you want. Accepted values are 'HHMMSS', 'HHMM', '24HHMMSS', and
- '24HHMM'. If the format is specified, the other two arguments will be ignored.
-
- 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. (This value is ignored if the *format*
- parameter is specified.)
-
- displaySeconds
- If True, control will include a seconds field; if False, it will
- just show hours and minutes. (This value is ignored if the *format*
- parameter is specified.)
-
- spinButton
- If specified, this button's events will be bound to the behavior of the
- TimeCtrl, working like up/down cursor key events. (See BindSpinButton.)
-
- min
- Defines the lower bound for "valid" selections in the control.
- By default, TimeCtrl doesn't have bounds. You must set both upper and lower
- bounds to make the control pay attention to them, (as only one bound makes no sense
- with times.) "Valid" times will fall between the min and max "pie wedge" of the
- clock.
- max
- Defines the upper bound for "valid" selections in the control.
- "Valid" times will fall between the min and max "pie wedge" of the
- clock. (This can be a "big piece", ie. min = 11pm, max= 10pm
- means *all but the hour from 10:00pm to 11pm are valid times.*)
- limited
- If True, the control will not permit entry of values that fall outside the
- set bounds.
-
- oob_color
- Sets the background color used to indicate out-of-bounds values for the control
- when the control is not limited. This is set to "Yellow" by default.
-
---------------------
-
-EVT_TIMEUPDATE(win, id, func)
- func is fired whenever the value of the control changes.
-
-
-SetValue(time_string | wxDateTime | wxTimeSpan | mx.DateTime | mx.DateTimeDelta)
- Sets the value of the control to a particular time, given a valid
- value; raises ValueError on invalid value.
-
-*NOTE:* This will only allow mx.DateTime or mx.DateTimeDelta if mx.DateTime
- was successfully imported by the class module.
-
-GetValue(as_wxDateTime = False, as_mxDateTime = False, as_wxTimeSpan=False, as mxDateTimeDelta=False)
- Retrieves the value of the time from the control. By default this is
- returned as a string, unless one of the other arguments is set; args are
- searched in the order listed; only one value will be returned.
-
-GetWxDateTime(value=None)
- When called without arguments, retrieves the value of the control, and applies
- it to the wxDateTimeFromHMS() constructor, and returns the resulting value.
- The date portion will always be set to Jan 1, 1970. This form is the same
- as GetValue(as_wxDateTime=True). GetWxDateTime can also be called with any of the
- other valid time formats settable with SetValue, to regularize it to a single
- wxDateTime form. The function will raise ValueError on an unconvertable argument.
-
-GetMxDateTime()
- Retrieves the value of the control and applies it to the DateTime.Time()
- constructor,and returns the resulting value. (The date portion will always be
- set to Jan 1, 1970.) (Same as GetValue(as_wxDateTime=True); provided for backward
- compatibility with previous release.)
-
-
-BindSpinButton(SpinBtton)
- 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.)
-
-
-SetMin(min=None)
- Sets the expected minimum value, or lower bound, of the control.
- (The lower bound will only be enforced if the control is
- configured to limit its values to the set bounds.)
- If a value of *None* is provided, then the control will have
- explicit lower bound. If the value specified is greater than
- the current lower bound, then the function returns False and the
- lower bound will not change from its current setting. On success,
- the function returns True. Even if set, if there is no corresponding
- upper bound, the control will behave as if it is unbounded.
-
- If successful and the current value is outside the
- new bounds, if the control is limited the value will be
- automatically adjusted to the nearest bound; if not limited,
- the background of the control will be colored with the current
- out-of-bounds color.
-
-GetMin(as_string=False)
- Gets the current lower bound value for the control, returning
- None, if not set, or a wxDateTime, unless the as_string parameter
- is set to True, at which point it will return the string
- representation of the lower bound.
-
-
-SetMax(max=None)
- Sets the expected maximum value, or upper bound, of the control.
- (The upper bound will only be enforced if the control is
- configured to limit its values to the set bounds.)
- If a value of *None* is provided, then the control will
- have no explicit upper bound. If the value specified is less
- than the current lower bound, then the function returns False and
- the maximum will not change from its current setting. On success,
- the function returns True. Even if set, if there is no corresponding
- lower bound, the control will behave as if it is unbounded.
-
- If successful and the current value is outside the
- new bounds, if the control is limited the value will be
- automatically adjusted to the nearest bound; if not limited,
- the background of the control will be colored with the current
- out-of-bounds color.
-
-GetMax(as_string = False)
- Gets the current upper bound value for the control, returning
- None, if not set, or a wxDateTime, unless the as_string parameter
- is set to True, at which point it will return the string
- representation of the lower bound.
-
-
-
-SetBounds(min=None,max=None)
- This function is a convenience function for setting the min and max
- values at the same time. The function only applies the maximum bound
- if setting the minimum bound is successful, and returns True
- only if both operations succeed. *Note: leaving out an argument
- will remove the corresponding bound, and result in the behavior of
- an unbounded control.*
-
-GetBounds(as_string = False)
- This function returns a two-tuple (min,max), indicating the
- current bounds of the control. Each value can be None if
- that bound is not set. The values will otherwise be wxDateTimes
- unless the as_string argument is set to True, at which point they
- will be returned as string representations of the bounds.
-
-
-IsInBounds(value=None)
- Returns *True* if no value is specified and the current value
- of the control falls within the current bounds. This function can also
- be called with a value to see if that value would fall within the current
- bounds of the given control. It will raise ValueError if the value
- specified is not a wxDateTime, mxDateTime (if available) or parsable string.
-
-
-IsValid(value)
- Returns *True* if specified value is a legal time value and
- falls within the current bounds of the given control.
-
-
-SetLimited(bool)
- If called with a value of True, this function will cause the control
- to limit the value to fall within the bounds currently specified.
- (Provided both bounds have been set.)
- If the control's value currently exceeds the bounds, it will then
- be set to the nearest bound.
- If called with a value of False, this function will disable value
- limiting, but coloring of out-of-bounds values will still take
- place if bounds have been set for the control.
-IsLimited()
- Returns *True* if the control is currently limiting the
- value to fall within the current bounds.
-
-
-"""
-
-import copy
-import string
-import types
-
-import wx
-
-from wx.tools.dbg import Logger
-from wx.lib.masked import Field, BaseMaskedTextCtrl
-
-dbg = Logger()
-##dbg(enable=0)
-
-try:
- from mx import DateTime
- accept_mx = True
-except ImportError:
- accept_mx = False
-
-# This class of event fires whenever the value of the time changes in the control:
-wxEVT_TIMEVAL_UPDATED = wx.NewEventType()
-EVT_TIMEUPDATE = wx.PyEventBinder(wxEVT_TIMEVAL_UPDATED, 1)
-
-class TimeUpdatedEvent(wx.PyCommandEvent):
- """
- Used to fire an EVT_TIMEUPDATE event whenever the value in a TimeCtrl changes.
- """
- def __init__(self, id, value ='12:00:00 AM'):
- wx.PyCommandEvent.__init__(self, wxEVT_TIMEVAL_UPDATED, id)
- self.value = value
- def GetValue(self):
- """Retrieve the value of the time control at the time this event was generated"""
- return self.value
-
-class TimeCtrlAccessorsMixin:
- """
- Defines TimeCtrl's list of attributes having their own Get/Set functions,
- ignoring those in the base class that make no sense for a time control.
- """
- exposed_basectrl_params = (
- 'defaultValue',
- 'description',
-
- 'useFixedWidthFont',
- 'emptyBackgroundColour',
- 'validBackgroundColour',
- 'invalidBackgroundColour',
-
- 'validFunc',
- 'validRequired',
- )
- for param in exposed_basectrl_params:
- propname = param[0].upper() + param[1:]
- exec('def Set%s(self, value): self.SetCtrlParameters(%s=value)' % (propname, param))
- exec('def Get%s(self): return self.GetCtrlParameter("%s")''' % (propname, param))
-
- if param.find('Colour') != -1:
- # add non-british spellings, for backward-compatibility
- propname.replace('Colour', 'Color')
-
- exec('def Set%s(self, value): self.SetCtrlParameters(%s=value)' % (propname, param))
- exec('def Get%s(self): return self.GetCtrlParameter("%s")''' % (propname, param))
-
-
-class TimeCtrl(BaseMaskedTextCtrl):
- """
- Masked control providing several time formats and manipulation of time values.
- """
-
- valid_ctrl_params = {
- 'format' : 'HHMMSS', # default format code
- 'displaySeconds' : True, # by default, shows seconds
- 'min': None, # by default, no bounds set
- 'max': None,
- 'limited': False, # by default, no limiting even if bounds set
- 'useFixedWidthFont': True, # by default, use a fixed-width font
- 'oob_color': "Yellow" # by default, the default masked.TextCtrl "invalid" color
- }
-
- def __init__ (
- self, parent, id=-1, value = '00:00:00',
- pos = wx.DefaultPosition, size = wx.DefaultSize,
- fmt24hr=False,
- spinButton = None,
- style = wx.TE_PROCESS_TAB,
- validator = wx.DefaultValidator,
- name = "time",
- **kwargs ):
-
- # set defaults for control:
-## dbg('setting defaults:')
-
- self.__fmt24hr = False
- wxdt = wx.DateTimeFromDMY(1, 0, 1970)
- try:
- if wxdt.Format('%p') != 'AM':
- TimeCtrl.valid_ctrl_params['format'] = '24HHMMSS'
- self.__fmt24hr = True
- fmt24hr = True # force/change default positional argument
- # (will countermand explicit set to False too.)
- except:
- TimeCtrl.valid_ctrl_params['format'] = '24HHMMSS'
- self.__fmt24hr = True
- fmt24hr = True # force/change default positional argument
- # (will countermand explicit set to False too.)
-
- for key, param_value in TimeCtrl.valid_ctrl_params.items():
- # This is done this way to make setattr behave consistently with
- # "private attribute" name mangling
- setattr(self, "_TimeCtrl__" + key, copy.copy(param_value))
-
- # create locals from current defaults, so we can override if
- # specified in kwargs, and handle uniformly:
- min = self.__min
- max = self.__max
- limited = self.__limited
- self.__posCurrent = 0
- # handle deprecated keword argument name:
- if kwargs.has_key('display_seconds'):
- kwargs['displaySeconds'] = kwargs['display_seconds']
- del kwargs['display_seconds']
- if not kwargs.has_key('displaySeconds'):
- kwargs['displaySeconds'] = True
-
- # (handle positional arg (from original release) differently from rest of kwargs:)
- if not kwargs.has_key('format'):
- if fmt24hr:
- if kwargs.has_key('displaySeconds') and kwargs['displaySeconds']:
- kwargs['format'] = '24HHMMSS'
- del kwargs['displaySeconds']
- else:
- kwargs['format'] = '24HHMM'
- else:
- if kwargs.has_key('displaySeconds') and kwargs['displaySeconds']:
- kwargs['format'] = 'HHMMSS'
- del kwargs['displaySeconds']
- else:
- kwargs['format'] = 'HHMM'
-
- if not kwargs.has_key('useFixedWidthFont'):
- # allow control over font selection:
- kwargs['useFixedWidthFont'] = self.__useFixedWidthFont
-
- maskededit_kwargs = self.SetParameters(**kwargs)
-
- # allow for explicit size specification:
- if size != wx.DefaultSize:
- # override (and remove) "autofit" autoformat code in standard time formats:
- maskededit_kwargs['formatcodes'] = 'T!'
-
- # This allows range validation if set
- maskededit_kwargs['validFunc'] = self.IsInBounds
-
- # This allows range limits to affect insertion into control or not
- # dynamically without affecting individual field constraint validation
- maskededit_kwargs['retainFieldValidation'] = True
-
- # Now we can initialize the base control:
- BaseMaskedTextCtrl.__init__(
- self, parent, id=id,
- pos=pos, size=size,
- style = style,
- validator = validator,
- name = name,
- setupEventHandling = False,
- **maskededit_kwargs)
-
-
- # This makes ':' act like tab (after we fix each ':' key event to remove "shift")
- self._SetKeyHandler(':', self._OnChangeField)
-
-
- # This makes the up/down keys act like spin button controls:
- self._SetKeycodeHandler(wx.WXK_UP, self.__OnSpinUp)
- self._SetKeycodeHandler(wx.WXK_DOWN, self.__OnSpinDown)
-
-
- # This allows ! and c/C to set the control to the current time:
- self._SetKeyHandler('!', self.__OnSetToNow)
- self._SetKeyHandler('c', self.__OnSetToNow)
- self._SetKeyHandler('C', self.__OnSetToNow)
-
-
- # Set up event handling ourselves, so we can insert special
- # processing on the ":' key to remove the "shift" attribute
- # *before* the default handlers have been installed, so
- # that : takes you forward, not back, and so we can issue
- # EVT_TIMEUPDATE events on changes:
-
- self.Bind(wx.EVT_SET_FOCUS, self._OnFocus ) ## defeat automatic full selection
- self.Bind(wx.EVT_KILL_FOCUS, self._OnKillFocus ) ## run internal validator
- self.Bind(wx.EVT_LEFT_UP, self.__LimitSelection) ## limit selections to single field
- self.Bind(wx.EVT_LEFT_DCLICK, self._OnDoubleClick ) ## select field under cursor on dclick
- self.Bind(wx.EVT_KEY_DOWN, self._OnKeyDown ) ## capture control events not normally seen, eg ctrl-tab.
- self.Bind(wx.EVT_CHAR, self.__OnChar ) ## remove "shift" attribute from colon key event,
- ## then call BaseMaskedTextCtrl._OnChar with
- ## the possibly modified event.
- self.Bind(wx.EVT_TEXT, self.__OnTextChange, self ) ## color control appropriately and EVT_TIMEUPDATE events
-
-
- # Validate initial value and set if appropriate
- try:
- self.SetBounds(min, max)
- self.SetLimited(limited)
- self.SetValue(value)
- except:
- self.SetValue('00:00:00')
-
- if spinButton:
- self.BindSpinButton(spinButton) # bind spin button up/down events to this control
-
-
- def SetParameters(self, **kwargs):
- """
- Function providing access to the parameters governing TimeCtrl display and bounds.
- """
-## dbg('TimeCtrl::SetParameters(%s)' % repr(kwargs), indent=1)
- maskededit_kwargs = {}
- reset_format = False
-
- if kwargs.has_key('display_seconds'):
- kwargs['displaySeconds'] = kwargs['display_seconds']
- del kwargs['display_seconds']
- if kwargs.has_key('format') and kwargs.has_key('displaySeconds'):
- del kwargs['displaySeconds'] # always apply format if specified
-
- # assign keyword args as appropriate:
- for key, param_value in kwargs.items():
- if key not in TimeCtrl.valid_ctrl_params.keys():
- raise AttributeError('invalid keyword argument "%s"' % key)
-
- if key == 'format':
- wxdt = wx.DateTimeFromDMY(1, 0, 1970)
- try:
- if wxdt.Format('%p') != 'AM':
- require24hr = True
- else:
- require24hr = False
- except:
- require24hr = True
-
- # handle both local or generic 'maskededit' autoformat codes:
- if param_value == 'HHMMSS' or param_value == 'TIMEHHMMSS':
- self.__displaySeconds = True
- self.__fmt24hr = False
- elif param_value == 'HHMM' or param_value == 'TIMEHHMM':
- self.__displaySeconds = False
- self.__fmt24hr = False
- elif param_value == '24HHMMSS' or param_value == '24HRTIMEHHMMSS':
- self.__displaySeconds = True
- self.__fmt24hr = True
- elif param_value == '24HHMM' or param_value == '24HRTIMEHHMM':
- self.__displaySeconds = False
- self.__fmt24hr = True
- else:
- raise AttributeError('"%s" is not a valid format' % param_value)
-
- if require24hr and not self.__fmt24hr:
- raise AttributeError('"%s" is an unsupported time format for the current locale' % param_value)
-
- reset_format = True
-
- elif key in ("displaySeconds", "display_seconds") and not kwargs.has_key('format'):
- self.__displaySeconds = param_value
- reset_format = True
-
- elif key == "min": min = param_value
- elif key == "max": max = param_value
- elif key == "limited": limited = param_value
-
- elif key == "useFixedWidthFont":
- maskededit_kwargs[key] = param_value
-
- elif key == "oob_color":
- maskededit_kwargs['invalidBackgroundColor'] = param_value
-
- if reset_format:
- if self.__fmt24hr:
- if self.__displaySeconds: maskededit_kwargs['autoformat'] = '24HRTIMEHHMMSS'
- else: maskededit_kwargs['autoformat'] = '24HRTIMEHHMM'
-
- # Set hour field to zero-pad, right-insert, require explicit field change,
- # select entire field on entry, and require a resultant valid entry
- # to allow character entry:
- hourfield = Field(formatcodes='0r<SV', validRegex='0\d|1\d|2[0123]', validRequired=True)
- else:
- if self.__displaySeconds: maskededit_kwargs['autoformat'] = 'TIMEHHMMSS'
- else: maskededit_kwargs['autoformat'] = 'TIMEHHMM'
-
- # Set hour field to allow spaces (at start), right-insert,
- # require explicit field change, select entire field on entry,
- # and require a resultant valid entry to allow character entry:
- hourfield = Field(formatcodes='_0<rSV', validRegex='0[1-9]| [1-9]|1[012]', validRequired=True)
- ampmfield = Field(formatcodes='S', emptyInvalid = True, validRequired = True)
-
- # Field 1 is always a zero-padded right-insert minute field,
- # similarly configured as above:
- minutefield = Field(formatcodes='0r<SV', validRegex='[0-5]\d', validRequired=True)
-
- fields = [ hourfield, minutefield ]
- if self.__displaySeconds:
- fields.append(copy.copy(minutefield)) # second field has same constraints as field 1
-
- if not self.__fmt24hr:
- fields.append(ampmfield)
-
- # set fields argument:
- maskededit_kwargs['fields'] = fields
-
- # This allows range validation if set
- maskededit_kwargs['validFunc'] = self.IsInBounds
-
- # This allows range limits to affect insertion into control or not
- # dynamically without affecting individual field constraint validation
- maskededit_kwargs['retainFieldValidation'] = True
-
- if hasattr(self, 'controlInitialized') and self.controlInitialized:
- self.SetCtrlParameters(**maskededit_kwargs) # set appropriate parameters
-
- # Validate initial value and set if appropriate
- try:
- self.SetBounds(min, max)
- self.SetLimited(limited)
- self.SetValue(value)
- except:
- self.SetValue('00:00:00')
-## dbg(indent=0)
- return {} # no arguments to return
- else:
-## dbg(indent=0)
- return maskededit_kwargs
-
-
- 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('TimeCtrl::BindSpinButton')
- self.__spinButton = sb
- if self.__spinButton:
- # bind event handlers to spin ctrl
- self.__spinButton.Bind(wx.EVT_SPIN_UP, self.__OnSpinUp, self.__spinButton)
- self.__spinButton.Bind(wx.EVT_SPIN_DOWN, self.__OnSpinDown, self.__spinButton)
-
-
- def __repr__(self):
- return "<TimeCtrl: %s>" % self.GetValue()
-
-
- def SetValue(self, value):
- """
- Validating SetValue function for time values:
- This function will do dynamic type checking on the value argument,
- and convert wxDateTime, mxDateTime, or 12/24 format time string
- into the appropriate format string for the control.
- """
-## dbg('TimeCtrl::SetValue(%s)' % repr(value), indent=1)
- try:
- strtime = self._toGUI(self.__validateValue(value))
- except:
-## dbg('validation failed', indent=0)
- raise
-
-## dbg('strtime:', strtime)
- self._SetValue(strtime)
-## dbg(indent=0)
-
- def GetValue(self,
- as_wxDateTime = False,
- as_mxDateTime = False,
- as_wxTimeSpan = False,
- as_mxDateTimeDelta = False):
- """
- This function returns the value of the display as a string by default, but
- supports return as a wx.DateTime, mx.DateTime, wx.TimeSpan, or mx.DateTimeDelta,
- if requested. (Evaluated in the order above-- first one wins!)
- """
-
-
- if as_wxDateTime or as_mxDateTime or as_wxTimeSpan or as_mxDateTimeDelta:
- value = self.GetWxDateTime()
- if as_wxDateTime:
- pass
- elif as_mxDateTime:
- value = DateTime.DateTime(1970, 1, 1, value.GetHour(), value.GetMinute(), value.GetSecond())
- elif as_wxTimeSpan:
- value = wx.TimeSpan(value.GetHour(), value.GetMinute(), value.GetSecond())
- elif as_mxDateTimeDelta:
- value = DateTime.DateTimeDelta(0, value.GetHour(), value.GetMinute(), value.GetSecond())
- else:
- value = BaseMaskedTextCtrl.GetValue(self)
- return value
-
-
- def SetWxDateTime(self, wxdt):
- """
- Because SetValue can take a wx.DateTime, this is now just an alias.
- """
- self.SetValue(wxdt)
-
-
- def GetWxDateTime(self, value=None):
- """
- This function is the conversion engine for TimeCtrl; it takes
- one of the following types:
- time string
- wxDateTime
- wxTimeSpan
- mxDateTime
- mxDateTimeDelta
- and converts it to a wx.DateTime that always has Jan 1, 1970 as its date
- portion, so that range comparisons around values can work using
- wx.DateTime's built-in comparison function. If a value is not
- provided to convert, the string value of the control will be used.
- If the value is not one of the accepted types, a ValueError will be
- raised.
- """
- global accept_mx
-## dbg(suspend=1)
-## dbg('TimeCtrl::GetWxDateTime(%s)' % repr(value), indent=1)
- if value is None:
-## dbg('getting control value')
- value = self.GetValue()
-## dbg('value = "%s"' % value)
-
- if type(value) == types.UnicodeType:
- value = str(value) # convert to regular string
-
- valid = True # assume true
- if type(value) == types.StringType:
-
- # Construct constant wxDateTime, then try to parse the string:
- wxdt = wx.DateTimeFromDMY(1, 0, 1970)
-## dbg('attempting conversion')
- value = value.strip() # (parser doesn't like leading spaces)
- checkTime = wxdt.ParseTime(value)
- valid = checkTime == len(value) # entire string parsed?
-## dbg('checkTime == len(value)?', valid)
-
- if not valid:
- # deal with bug/deficiency in wx.DateTime:
- try:
- if wxdt.Format('%p') not in ('AM', 'PM') and checkTime in (5,8):
- # couldn't parse the AM/PM field
- raise ValueError('cannot convert string "%s" to valid time for the current locale; please use 24hr time instead' % value)
- else:
- ## dbg(indent=0, suspend=0)
- raise ValueError('cannot convert string "%s" to valid time' % value)
- except:
- raise ValueError('cannot convert string "%s" to valid time for the current locale; please use 24hr time instead' % value)
-
- else:
- if isinstance(value, wx.DateTime):
- hour, minute, second = value.GetHour(), value.GetMinute(), value.GetSecond()
- elif isinstance(value, wx.TimeSpan):
- totalseconds = value.GetSeconds()
- hour = totalseconds / 3600
- minute = totalseconds / 60 - (hour * 60)
- second = totalseconds - ((hour * 3600) + (minute * 60))
-
- elif accept_mx and isinstance(value, DateTime.DateTimeType):
- hour, minute, second = value.hour, value.minute, value.second
- elif accept_mx and isinstance(value, DateTime.DateTimeDeltaType):
- hour, minute, second = value.hour, value.minute, value.second
- else:
- # Not a valid function argument
- if accept_mx:
- error = 'GetWxDateTime requires wxDateTime, mxDateTime or parsable time string, passed %s'% repr(value)
- else:
- error = 'GetWxDateTime requires wxDateTime or parsable time string, passed %s'% repr(value)
-## dbg(indent=0, suspend=0)
- raise ValueError(error)
-
- wxdt = wx.DateTimeFromDMY(1, 0, 1970)
- wxdt.SetHour(hour)
- wxdt.SetMinute(minute)
- wxdt.SetSecond(second)
-
-## dbg('wxdt:', wxdt, indent=0, suspend=0)
- return wxdt
-
-
- def SetMxDateTime(self, mxdt):
- """
- Because SetValue can take an mx.DateTime, (if DateTime is importable),
- this is now just an alias.
- """
- self.SetValue(value)
-
-
- def GetMxDateTime(self, value=None):
- """
- Returns the value of the control as an mx.DateTime, with the date
- portion set to January 1, 1970.
- """
- if value is None:
- t = self.GetValue(as_mxDateTime=True)
- else:
- # Convert string 1st to wxDateTime, then use components, since
- # mx' DateTime.Parser.TimeFromString() doesn't handle AM/PM:
- wxdt = self.GetWxDateTime(value)
- hour, minute, second = wxdt.GetHour(), wxdt.GetMinute(), wxdt.GetSecond()
- t = DateTime.DateTime(1970,1,1) + DateTimeDelta(0, hour, minute, second)
- return t
-
-
- def SetMin(self, min=None):
- """
- Sets the minimum value of the control. If a value of None
- is provided, then the control will have no explicit minimum value.
- If the value specified is greater than the current maximum value,
- then the function returns 0 and the minimum will not change from
- its current setting. On success, the function returns 1.
-
- If successful and the current value is lower than the new lower
- bound, if the control is limited, the value will be automatically
- adjusted to the new minimum value; if not limited, the value in the
- control will be colored as invalid.
- """
-## dbg('TimeCtrl::SetMin(%s)'% repr(min), indent=1)
- if min is not None:
- try:
- min = self.GetWxDateTime(min)
- self.__min = self._toGUI(min)
- except:
-## dbg('exception occurred', indent=0)
- return False
- else:
- self.__min = min
-
- if self.IsLimited() and not self.IsInBounds():
- self.SetLimited(self.__limited) # force limited value:
- else:
- self._CheckValid()
- ret = True
-## dbg('ret:', ret, indent=0)
- return ret
-
-
- def GetMin(self, as_string = False):
- """
- Gets the minimum value of the control.
- If None, it will return None. Otherwise it will return
- the current minimum bound on the control, as a wxDateTime
- by default, or as a string if as_string argument is True.
- """
-## dbg(suspend=1)
-## dbg('TimeCtrl::GetMin, as_string?', as_string, indent=1)
- if self.__min is None:
-## dbg('(min == None)')
- ret = self.__min
- elif as_string:
- ret = self.__min
-## dbg('ret:', ret)
- else:
- try:
- ret = self.GetWxDateTime(self.__min)
- except:
-## dbg(suspend=0)
-## dbg('exception occurred', indent=0)
- raise
-## dbg('ret:', repr(ret))
-## dbg(indent=0, suspend=0)
- return ret
-
-
- def SetMax(self, max=None):
- """
- Sets the maximum value of the control. If a value of None
- is provided, then the control will have no explicit maximum value.
- If the value specified is less than the current minimum value, then
- the function returns False and the maximum will not change from its
- current setting. On success, the function returns True.
-
- If successful and the current value is greater than the new upper
- bound, if the control is limited the value will be automatically
- adjusted to this maximum value; if not limited, the value in the
- control will be colored as invalid.
- """
-## dbg('TimeCtrl::SetMax(%s)' % repr(max), indent=1)
- if max is not None:
- try:
- max = self.GetWxDateTime(max)
- self.__max = self._toGUI(max)
- except:
-## dbg('exception occurred', indent=0)
- return False
- else:
- self.__max = max
-## dbg('max:', repr(self.__max))
- if self.IsLimited() and not self.IsInBounds():
- self.SetLimited(self.__limited) # force limited value:
- else:
- self._CheckValid()
- ret = True
-## dbg('ret:', ret, indent=0)
- return ret
-
-
- def GetMax(self, as_string = False):
- """
- Gets the minimum value of the control.
- If None, it will return None. Otherwise it will return
- the current minimum bound on the control, as a wxDateTime
- by default, or as a string if as_string argument is True.
- """
-## dbg(suspend=1)
-## dbg('TimeCtrl::GetMin, as_string?', as_string, indent=1)
- if self.__max is None:
-## dbg('(max == None)')
- ret = self.__max
- elif as_string:
- ret = self.__max
-## dbg('ret:', ret)
- else:
- try:
- ret = self.GetWxDateTime(self.__max)
- except:
-## dbg(suspend=0)
-## dbg('exception occurred', indent=0)
- raise
-## dbg('ret:', repr(ret))
-## dbg(indent=0, suspend=0)
- return ret
-
-
- def SetBounds(self, min=None, max=None):
- """
- This function is a convenience function for setting the min and max
- values at the same time. The function only applies the maximum bound
- if setting the minimum bound is successful, and returns True
- only if both operations succeed.
- **NOTE:** leaving out an argument will remove the corresponding bound.
- """
- ret = self.SetMin(min)
- return ret and self.SetMax(max)
-
-
- def GetBounds(self, as_string = False):
- """
- This function returns a two-tuple (min,max), indicating the
- current bounds of the control. Each value can be None if
- that bound is not set.
- """
- return (self.GetMin(as_string), self.GetMax(as_string))
-
-
- def SetLimited(self, limited):
- """
- If called with a value of True, this function will cause the control
- to limit the value to fall within the bounds currently specified.
- If the control's value currently exceeds the bounds, it will then
- be limited accordingly.
-
- If called with a value of 0, this function will disable value
- limiting, but coloring of out-of-bounds values will still take
- place if bounds have been set for the control.
- """
-## dbg('TimeCtrl::SetLimited(%d)' % limited, indent=1)
- self.__limited = limited
-
- if not limited:
- self.SetMaskParameters(validRequired = False)
- self._CheckValid()
-## dbg(indent=0)
- return
-
-## dbg('requiring valid value')
- self.SetMaskParameters(validRequired = True)
-
- min = self.GetMin()
- max = self.GetMax()
- if min is None or max is None:
-## dbg('both bounds not set; no further action taken')
- return # can't limit without 2 bounds
-
- elif not self.IsInBounds():
- # set value to the nearest bound:
- try:
- value = self.GetWxDateTime()
- except:
-## dbg('exception occurred', indent=0)
- raise
-
- if min <= max: # valid range doesn't span midnight
-## dbg('min <= max')
- # which makes the "nearest bound" computation trickier...
-
- # determine how long the "invalid" pie wedge is, and cut
- # this interval in half for comparison purposes:
-
- # Note: relies on min and max and value date portions
- # always being the same.
- interval = (min + wx.TimeSpan(24, 0, 0, 0)) - max
-
- half_interval = wx.TimeSpan(
- 0, # hours
- 0, # minutes
- interval.GetSeconds() / 2, # seconds
- 0) # msec
-
- if value < min: # min is on next day, so use value on
- # "next day" for "nearest" interval calculation:
- cmp_value = value + wx.TimeSpan(24, 0, 0, 0)
- else: # "before midnight; ok
- cmp_value = value
-
- if (cmp_value - max) > half_interval:
-## dbg('forcing value to min (%s)' % min.FormatTime())
- self.SetValue(min)
- else:
-## dbg('forcing value to max (%s)' % max.FormatTime())
- self.SetValue(max)
- else:
-## dbg('max < min')
- # therefore max < value < min guaranteed to be true,
- # so "nearest bound" calculation is much easier:
- if (value - max) >= (min - value):
- # current value closer to min; pick that edge of pie wedge
-## dbg('forcing value to min (%s)' % min.FormatTime())
- self.SetValue(min)
- else:
-## dbg('forcing value to max (%s)' % max.FormatTime())
- self.SetValue(max)
-
-## dbg(indent=0)
-
-
-
- def IsLimited(self):
- """
- Returns True if the control is currently limiting the
- value to fall within any current bounds. *Note:* can
- be set even if there are no current bounds.
- """
- return self.__limited
-
-
- def IsInBounds(self, value=None):
- """
- Returns True if no value is specified and the current value
- of the control falls within the current bounds. As the clock
- is a "circle", both minimum and maximum bounds must be set for
- a value to ever be considered "out of bounds". This function can
- also be called with a value to see if that value would fall within
- the current bounds of the given control.
- """
- if value is not None:
- try:
- value = self.GetWxDateTime(value) # try to regularize passed value
- except ValueError:
-## dbg('ValueError getting wxDateTime for %s' % repr(value), indent=0)
- raise
-
-## dbg('TimeCtrl::IsInBounds(%s)' % repr(value), indent=1)
- if self.__min is None or self.__max is None:
-## dbg(indent=0)
- return True
-
- elif value is None:
- try:
- value = self.GetWxDateTime()
- except:
-## dbg('exception occurred', indent=0)
- raise
-
-## dbg('value:', value.FormatTime())
-
- # Get wxDateTime representations of bounds:
- min = self.GetMin()
- max = self.GetMax()
-
- midnight = wx.DateTimeFromDMY(1, 0, 1970)
- if min <= max: # they don't span midnight
- ret = min <= value <= max
-
- else:
- # have to break into 2 tests; to be in bounds
- # either "min" <= value (<= midnight of *next day*)
- # or midnight <= value <= "max"
- ret = min <= value or (midnight <= value <= max)
-## dbg('in bounds?', ret, indent=0)
- return ret
-
-
- def IsValid( self, value ):
- """
- Can be used to determine if a given value would be a legal and
- in-bounds value for the control.
- """
- try:
- self.__validateValue(value)
- return True
- except ValueError:
- return False
-
- def SetFormat(self, format):
- self.SetParameters(format=format)
-
- def GetFormat(self):
- if self.__displaySeconds:
- if self.__fmt24hr: return '24HHMMSS'
- else: return 'HHMMSS'
- else:
- if self.__fmt24hr: return '24HHMM'
- else: return 'HHMM'
-
-#-------------------------------------------------------------------------------------------------------------
-# these are private functions and overrides:
-
-
- def __OnTextChange(self, event=None):
-## dbg('TimeCtrl::OnTextChange', indent=1)
-
- # Allow Maskedtext base control to color as appropriate,
- # and Skip the EVT_TEXT event (if appropriate.)
- ##! WS: For some inexplicable reason, every wxTextCtrl.SetValue()
- ## call is generating two (2) EVT_TEXT events. (!)
- ## The the only mechanism I can find to mask this problem is to
- ## keep track of last value seen, and declare a valid EVT_TEXT
- ## event iff the value has actually changed. The masked edit
- ## OnTextChange routine does this, and returns True on a valid event,
- ## False otherwise.
- if not BaseMaskedTextCtrl._OnTextChange(self, event):
- return
-
-## dbg('firing TimeUpdatedEvent...')
- evt = TimeUpdatedEvent(self.GetId(), self.GetValue())
- evt.SetEventObject(self)
- self.GetEventHandler().ProcessEvent(evt)
-## dbg(indent=0)
-
-
- def SetInsertionPoint(self, pos):
- """
- This override records the specified position and associated cell before
- calling base class' function. This is necessary to handle the optional
- spin button, because the insertion point is lost when the focus shifts
- to the spin button.
- """
-## dbg('TimeCtrl::SetInsertionPoint', pos, indent=1)
- BaseMaskedTextCtrl.SetInsertionPoint(self, pos) # (causes EVT_TEXT event to fire)
- self.__posCurrent = self.GetInsertionPoint()
-## dbg(indent=0)
-
-
- def SetSelection(self, sel_start, sel_to):
-## dbg('TimeCtrl::SetSelection', sel_start, sel_to, indent=1)
-
- # Adjust selection range to legal extent if not already
- if sel_start < 0:
- sel_start = 0
-
- if self.__posCurrent != sel_start: # force selection and insertion point to match
- self.SetInsertionPoint(sel_start)
- cell_start, cell_end = self._FindField(sel_start)._extent
- if not cell_start <= sel_to <= cell_end:
- sel_to = cell_end
-
- self.__bSelection = sel_start != sel_to
- BaseMaskedTextCtrl.SetSelection(self, sel_start, sel_to)
-## dbg(indent=0)
-
-
- def __OnSpin(self, key):
- """
- This is the function that gets called in response to up/down arrow or
- bound spin button events.
- """
- self.__IncrementValue(key, self.__posCurrent) # changes the value
-
- # Ensure adjusted control regains focus and has adjusted portion
- # selected:
- self.SetFocus()
- start, end = self._FindField(self.__posCurrent)._extent
- self.SetInsertionPoint(start)
- self.SetSelection(start, end)
-## dbg('current position:', self.__posCurrent)
-
-
- 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('TimeCtrl::OnSpinUp', indent=1)
- self.__OnSpin(wx.WXK_UP)
- keep_processing = False
-## dbg(indent=0)
- return keep_processing
-
-
- 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('TimeCtrl::OnSpinDown', indent=1)
- self.__OnSpin(wx.WXK_DOWN)
- keep_processing = False
-## dbg(indent=0)
- return keep_processing
-
-
- def __OnChar(self, event):
- """
- Handler to explicitly look for ':' keyevents, and if found,
- clear the m_shiftDown field, so it will behave as forward tab.
- It then calls the base control's _OnChar routine with the modified
- event instance.
- """
-## dbg('TimeCtrl::OnChar', indent=1)
- keycode = event.GetKeyCode()
-## dbg('keycode:', keycode)
- if keycode == ord(':'):
-## dbg('colon seen! removing shift attribute')
- event.m_shiftDown = False
- BaseMaskedTextCtrl._OnChar(self, event ) ## handle each keypress
-## dbg(indent=0)
-
-
- def __OnSetToNow(self, event):
- """
- This is the key handler for '!' and 'c'; this allows the user to
- quickly set the value of the control to the current time.
- """
- self.SetValue(wx.DateTime_Now().FormatTime())
- keep_processing = False
- return keep_processing
-
-
- def __LimitSelection(self, event):
- """
- Event handler for motion events; this handler
- changes limits the selection to the new cell boundaries.
- """
-## dbg('TimeCtrl::LimitSelection', indent=1)
- pos = self.GetInsertionPoint()
- self.__posCurrent = pos
- sel_start, sel_to = self.GetSelection()
- selection = sel_start != sel_to
- if selection:
- # only allow selection to end of current cell:
- start, end = self._FindField(sel_start)._extent
- if sel_to < pos: sel_to = start
- elif sel_to > pos: sel_to = end
-
-## dbg('new pos =', self.__posCurrent, 'select to ', sel_to)
- self.SetInsertionPoint(self.__posCurrent)
- self.SetSelection(self.__posCurrent, sel_to)
- if event: event.Skip()
-## dbg(indent=0)
-
-
- def __IncrementValue(self, key, pos):
-## dbg('TimeCtrl::IncrementValue', key, pos, indent=1)
- text = self.GetValue()
- field = self._FindField(pos)
-## dbg('field: ', field._index)
- start, end = field._extent
- slice = text[start:end]
- if key == wx.WXK_UP: increment = 1
- else: increment = -1
-
- if slice in ('A', 'P'):
- if slice == 'A': newslice = 'P'
- elif slice == 'P': newslice = 'A'
- newvalue = text[:start] + newslice + text[end:]
-
- elif field._index == 0:
- # adjusting this field is trickier, as its value can affect the
- # am/pm setting. So, we use wxDateTime to generate a new value for us:
- # (Use a fixed date not subject to DST variations:)
- converter = wx.DateTimeFromDMY(1, 0, 1970)
-## dbg('text: "%s"' % text)
- converter.ParseTime(text.strip())
- currenthour = converter.GetHour()
-## dbg('current hour:', currenthour)
- newhour = (currenthour + increment) % 24
-## dbg('newhour:', newhour)
- converter.SetHour(newhour)
-## dbg('converter.GetHour():', converter.GetHour())
- newvalue = converter # take advantage of auto-conversion for am/pm in .SetValue()
-
- else: # minute or second field; handled the same way:
- newslice = "%02d" % ((int(slice) + increment) % 60)
- newvalue = text[:start] + newslice + text[end:]
-
- try:
- self.SetValue(newvalue)
-
- except ValueError: # must not be in bounds:
- if not wx.Validator_IsSilent():
- wx.Bell()
-## dbg(indent=0)
-
-
- def _toGUI( self, wxdt ):
- """
- This function takes a wxdt as an unambiguous representation of a time, and
- converts it to a string appropriate for the format of the control.
- """
- if self.__fmt24hr:
- if self.__displaySeconds: strval = wxdt.Format('%H:%M:%S')
- else: strval = wxdt.Format('%H:%M')
- else:
- if self.__displaySeconds: strval = wxdt.Format('%I:%M:%S %p')
- else: strval = wxdt.Format('%I:%M %p')
-
- return strval
-
-
- def __validateValue( self, value ):
- """
- This function converts the value to a wxDateTime if not already one,
- does bounds checking and raises ValueError if argument is
- not a valid value for the control as currently specified.
- It is used by both the SetValue() and the IsValid() methods.
- """
-## dbg('TimeCtrl::__validateValue(%s)' % repr(value), indent=1)
- if not value:
-## dbg(indent=0)
- raise ValueError('%s not a valid time value' % repr(value))
-
- valid = True # assume true
- try:
- value = self.GetWxDateTime(value) # regularize form; can generate ValueError if problem doing so
- except:
-## dbg('exception occurred', indent=0)
- raise
-
- if self.IsLimited() and not self.IsInBounds(value):
-## dbg(indent=0)
- raise ValueError (
- 'value %s is not within the bounds of the control' % str(value) )
-## dbg(indent=0)
- return value
-
-#----------------------------------------------------------------------------
-# Test jig for TimeCtrl:
-
-if __name__ == '__main__':
- import traceback
-
- class TestPanel(wx.Panel):
- def __init__(self, parent, id,
- pos = wx.DefaultPosition, size = wx.DefaultSize,
- fmt24hr = 0, test_mx = 0,
- style = wx.TAB_TRAVERSAL ):
-
- wx.Panel.__init__(self, parent, id, pos, size, style)
-
- self.test_mx = test_mx
-
- self.tc = TimeCtrl(self, 10, fmt24hr = fmt24hr)
- sb = wx.SpinButton( self, 20, wx.DefaultPosition, (-1,20), 0 )
- self.tc.BindSpinButton(sb)
-
- sizer = wx.BoxSizer( wx.HORIZONTAL )
- sizer.Add( self.tc, 0, wx.ALIGN_CENTRE|wx.LEFT|wx.TOP|wx.BOTTOM, 5 )
- sizer.Add( sb, 0, wx.ALIGN_CENTRE|wx.RIGHT|wx.TOP|wx.BOTTOM, 5 )
-
- self.SetAutoLayout( True )
- self.SetSizer( sizer )
- sizer.Fit( self )
- sizer.SetSizeHints( self )
-
- self.Bind(EVT_TIMEUPDATE, self.OnTimeChange, self.tc)
-
- 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)
-
-
- class MyApp(wx.App):
- def OnInit(self):
- import sys
- fmt24hr = '24' in sys.argv
- test_mx = 'mx' in sys.argv
- try:
- frame = wx.Frame(None, -1, "TimeCtrl Test", (20,20), (100,100) )
- panel = TestPanel(frame, -1, (-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()
-__i=0
-
-## CHANGELOG:
-## ====================
-## Version 1.3
-## 1. Converted docstrings to reST format, added doc for ePyDoc.
-## 2. Renamed helper functions, vars etc. not intended to be visible in public
-## interface to code.
-##
-## Version 1.2
-## 1. Changed parameter name display_seconds to displaySeconds, to follow
-## other masked edit conventions.
-## 2. Added format parameter, to remove need to use both fmt24hr and displaySeconds.
-## 3. Changed inheritance to use BaseMaskedTextCtrl, to remove exposure of
-## nonsensical parameter methods from the control, so it will work
-## properly with Boa.