#
#----------------------------------------------------------------------------
-import wx
+"""
+Provides masked edit capabilities within a ComboBox format, as well as
+a base class from which you can derive masked comboboxes tailored to a specific
+function. See maskededit module overview for how to configure the control.
+"""
+
+import wx, types, string
from wx.lib.masked import *
# jmg 12/9/03 - when we cut ties with Py 2.2 and earlier, this would
# be a good place to implement the 2.3 logger class
from wx.tools.dbg import Logger
-dbg = Logger()
-##dbg(enable=0)
+##dbg = Logger()
+##dbg(enable=1)
## ---------- ---------- ---------- ---------- ---------- ---------- ----------
## Because calling SetSelection programmatically does not fire EVT_COMBOBOX
## events, we have to do it ourselves when we auto-complete.
class MaskedComboBoxSelectEvent(wx.PyCommandEvent):
+ """
+ Because calling SetSelection programmatically does not fire EVT_COMBOBOX
+ events, the derived control has to do it itself when it auto-completes.
+ """
def __init__(self, id, selection = 0, object=None):
wx.PyCommandEvent.__init__(self, wx.wxEVT_COMMAND_COMBOBOX_SELECTED, id)
class BaseMaskedComboBox( wx.ComboBox, MaskedEditMixin ):
"""
- This masked edit control adds the ability to use a masked input
- on a combobox, and do auto-complete of such values.
+ Base class for generic masked edit comboboxes; allows auto-complete of values.
+ It is not meant to be instantiated directly, but rather serves as a base class
+ for any subsequent refinements.
"""
def __init__( self, parent, id=-1, value = '',
pos = wx.DefaultPosition,
**kwargs):
- # This is necessary, because wxComboBox currently provides no
- # method for determining later if this was specified in the
- # constructor for the control...
- self.__readonly = style & wx.CB_READONLY == wx.CB_READONLY
-
kwargs['choices'] = choices ## set up maskededit to work with choice list too
## Since combobox completion is case-insensitive, always validate same way
choices=choices, style=style|wx.WANTS_CHARS,
validator=validator,
name=name)
-
self.controlInitialized = True
+ self._PostInit(style=style, setupEventHandling=setupEventHandling,
+ name=name, value=value, **kwargs)
+
+
+ def _PostInit(self, style=wx.CB_DROPDOWN,
+ setupEventHandling = True, ## setup event handling by default):
+ name = "maskedComboBox", value='', **kwargs):
+
+ # This is necessary, because wxComboBox currently provides no
+ # method for determining later if this was specified in the
+ # constructor for the control...
+ self.__readonly = style & wx.CB_READONLY == wx.CB_READONLY
+
+ if not hasattr(self, 'controlInitialized'):
+
+ self.controlInitialized = True ## must have been called via XRC, therefore base class is constructed
+ if not kwargs.has_key('choices'):
+ choices=[]
+ kwargs['choices'] = choices ## set up maskededit to work with choice list too
+ self._choices = []
+
+ ## Since combobox completion is case-insensitive, always validate same way
+ if not kwargs.has_key('compareNoCase'):
+ kwargs['compareNoCase'] = True
+
+ MaskedEditMixin.__init__( self, name, **kwargs )
+
+ self._choices = self._ctrl_constraints._choices
+## dbg('self._choices:', self._choices)
+
+ if self._ctrl_constraints._alignRight:
+ choices = [choice.rjust(self._masklength) for choice in choices]
+ else:
+ choices = [choice.ljust(self._masklength) for choice in choices]
+ wx.ComboBox.Clear(self)
+ wx.ComboBox.AppendItems(self, choices)
+
+
# Set control font - fixed width by default
self._setFont()
if self._autofit:
self.SetClientSize(self._CalcSize())
+ width = self.GetSize().width
+ height = self.GetBestSize().height
+ self.SetBestFittingSize((width, height))
+
if value:
# ensure value is width of the mask of the control:
self._SetInitialValue(value)
- self._SetKeycodeHandler(wx.WXK_UP, self.OnSelectChoice)
- self._SetKeycodeHandler(wx.WXK_DOWN, self.OnSelectChoice)
+ self._SetKeycodeHandler(wx.WXK_UP, self._OnSelectChoice)
+ self._SetKeycodeHandler(wx.WXK_DOWN, self._OnSelectChoice)
if setupEventHandling:
## Setup event handlers
self.Bind(wx.EVT_LEFT_DCLICK, self._OnDoubleClick) ## select field under cursor on dclick
self.Bind(wx.EVT_RIGHT_UP, self._OnContextMenu ) ## bring up an appropriate context menu
self.Bind(wx.EVT_CHAR, self._OnChar ) ## handle each keypress
- self.Bind(wx.EVT_KEY_DOWN, self.OnKeyDown ) ## for special processing of up/down keys
+ self.Bind(wx.EVT_KEY_DOWN, self._OnKeyDownInComboBox ) ## for special processing of up/down keys
self.Bind(wx.EVT_KEY_DOWN, self._OnKeyDown ) ## for processing the rest of the control keys
## (next in evt chain)
self.Bind(wx.EVT_TEXT, self._OnTextChange ) ## color control appropriately & keep
return (size[0]+20, size[1])
+ def SetFont(self, *args, **kwargs):
+ """ Set the font, then recalculate control size, if appropriate. """
+ wx.ComboBox.SetFont(self, *args, **kwargs)
+ if self._autofit:
+## dbg('calculated size:', self._CalcSize())
+ self.SetClientSize(self._CalcSize())
+ width = self.GetSize().width
+ height = self.GetBestSize().height
+## dbg('setting client size to:', (width, height))
+ self.SetBestFittingSize((width, height))
+
+
def _GetSelection(self):
"""
Allow mixin to get the text selection of this control.
REQUIRED by any class derived from MaskedEditMixin.
"""
+## dbg('MaskedComboBox::_GetSelection()')
return self.GetMark()
def _SetSelection(self, sel_start, sel_to):
Allow mixin to set the text selection of this control.
REQUIRED by any class derived from MaskedEditMixin.
"""
+## dbg('MaskedComboBox::_SetSelection: setting mark to (%d, %d)' % (sel_start, sel_to))
return self.SetMark( sel_start, sel_to )
def _GetInsertionPoint(self):
- return self.GetInsertionPoint()
+## dbg('MaskedComboBox::_GetInsertionPoint()', indent=1)
+## ret = self.GetInsertionPoint()
+ # work around new bug in 2.5, in which the insertion point
+ # returned is always at the right side of the selection,
+ # rather than the start, as is the case with TextCtrl.
+ ret = self.GetMark()[0]
+## dbg('returned', ret, indent=0)
+ return ret
def _SetInsertionPoint(self, pos):
+## dbg('MaskedComboBox::_SetInsertionPoint(%d)' % pos)
self.SetInsertionPoint(pos)
# make SetValue behave the same as if you had typed the value in:
try:
- value = self._Paste(value, raise_on_invalid=True, just_return_value=True)
+ value, replace_to = self._Paste(value, raise_on_invalid=True, just_return_value=True)
if self._isFloat:
self._isNeg = False # (clear current assumptions)
value = self._adjustFloat(value)
raise
self._SetValue(value)
-#### dbg('queuing insertion after .SetValue', self._masklength)
- wx.CallAfter(self._SetInsertionPoint, self._masklength)
- wx.CallAfter(self._SetSelection, self._masklength, self._masklength)
+#### dbg('queuing insertion after .SetValue', replace_to)
+ wx.CallAfter(self._SetInsertionPoint, replace_to)
+ wx.CallAfter(self._SetSelection, replace_to, replace_to)
def _Refresh(self):
else:
wx.ComboBox.Undo() # else revert to base control behavior
-
def Append( self, choice, clientData=None ):
"""
- This function override is necessary so we can keep track of any additions to the list
- of choices, because wxComboBox doesn't have an accessor for the choice list.
- The code here is the same as in the SetParameters() mixin function, but is
- done for the individual value as appended, so the list can be built incrementally
- without speed penalty.
+ This base control function override is necessary so the control can keep track
+ of any additions to the list of choices, because wx.ComboBox doesn't have an
+ accessor for the choice list. The code here is the same as in the
+ SetParameters() mixin function, but is done for the individual value
+ as appended, so the list can be built incrementally without speed penalty.
"""
if self._mask:
if type(choice) not in (types.StringType, types.UnicodeType):
wx.ComboBox.Append(self, choice, clientData)
+ def AppendItems( self, choices ):
+ """
+ AppendItems() is handled in terms of Append, to avoid code replication.
+ """
+ for choice in choices:
+ self.Append(choice)
+
def Clear( self ):
"""
- This function override is necessary so we can keep track of any additions to the list
- of choices, because wxComboBox doesn't have an accessor for the choice list.
+ This base control function override is necessary so the derived control can
+ keep track of any additions to the list of choices, because wx.ComboBox
+ doesn't have an accessor for the choice list.
"""
if self._mask:
self._choices = []
def _OnCtrlParametersChanged(self):
"""
- Override mixin's default OnCtrlParametersChanged to detect changes in choice list, so
- we can update the base control:
+ This overrides the mixin's default OnCtrlParametersChanged to detect
+ changes in choice list, so masked.Combobox can update the base control:
"""
if self.controlInitialized and self._choices != self._ctrl_constraints._choices:
wx.ComboBox.Clear(self)
wx.ComboBox.Append( self, choice )
- def GetMark(self):
- """
- This function is a hack to make up for the fact that wxComboBox has no
- method for returning the selected portion of its edit control. It
- works, but has the nasty side effect of generating lots of intermediate
- events.
- """
-## dbg(suspend=1) # turn off debugging around this function
-## dbg('MaskedComboBox::GetMark', indent=1)
- if self.__readonly:
-## dbg(indent=0)
- return 0, 0 # no selection possible for editing
-## sel_start, sel_to = wxComboBox.GetMark(self) # what I'd *like* to have!
- sel_start = sel_to = self.GetInsertionPoint()
-## dbg("current sel_start:", sel_start)
- value = self.GetValue()
-## dbg('value: "%s"' % value)
-
- self._ignoreChange = True # tell _OnTextChange() to ignore next event (if any)
-
- wx.ComboBox.Cut(self)
- newvalue = self.GetValue()
-## dbg("value after Cut operation:", newvalue)
-
- if newvalue != value: # something was selected; calculate extent
-## dbg("something selected")
- sel_to = sel_start + len(value) - len(newvalue)
- wx.ComboBox.SetValue(self, value) # restore original value and selection (still ignoring change)
- wx.ComboBox.SetInsertionPoint(self, sel_start)
- wx.ComboBox.SetMark(self, sel_start, sel_to)
-
- self._ignoreChange = False # tell _OnTextChange() to pay attn again
-
-## dbg('computed selection:', sel_start, sel_to, indent=0, suspend=0)
- return sel_start, sel_to
+ # Not all wx platform implementations have .GetMark, so we make the following test,
+ # and fall back to our old hack if they don't...
+ #
+ if not hasattr(wx.ComboBox, 'GetMark'):
+ def GetMark(self):
+ """
+ This function is a hack to make up for the fact that wx.ComboBox has no
+ method for returning the selected portion of its edit control. It
+ works, but has the nasty side effect of generating lots of intermediate
+ events.
+ """
+## dbg(suspend=1) # turn off debugging around this function
+## dbg('MaskedComboBox::GetMark', indent=1)
+ if self.__readonly:
+## dbg(indent=0)
+ return 0, 0 # no selection possible for editing
+## sel_start, sel_to = wxComboBox.GetMark(self) # what I'd *like* to have!
+ sel_start = sel_to = self.GetInsertionPoint()
+## dbg("current sel_start:", sel_start)
+ value = self.GetValue()
+## dbg('value: "%s"' % value)
+
+ self._ignoreChange = True # tell _OnTextChange() to ignore next event (if any)
+
+ wx.ComboBox.Cut(self)
+ newvalue = self.GetValue()
+## dbg("value after Cut operation:", newvalue)
+
+ if newvalue != value: # something was selected; calculate extent
+## dbg("something selected")
+ sel_to = sel_start + len(value) - len(newvalue)
+ wx.ComboBox.SetValue(self, value) # restore original value and selection (still ignoring change)
+ wx.ComboBox.SetInsertionPoint(self, sel_start)
+ wx.ComboBox.SetMark(self, sel_start, sel_to)
+
+ self._ignoreChange = False # tell _OnTextChange() to pay attn again
+
+## dbg('computed selection:', sel_start, sel_to, indent=0, suspend=0)
+ return sel_start, sel_to
+ else:
+ def GetMark(self):
+## dbg('MaskedComboBox::GetMark()', indent = 1)
+ ret = wx.ComboBox.GetMark(self)
+## dbg('returned', ret, indent=0)
+ return ret
def SetSelection(self, index):
"""
- Necessary for bookkeeping on choice selection, to keep current value
+ Necessary override for bookkeeping on choice selection, to keep current value
current.
"""
## dbg('MaskedComboBox::SetSelection(%d)' % index)
wx.ComboBox.SetSelection(self, index)
- def OnKeyDown(self, event):
+ def _OnKeyDownInComboBox(self, event):
"""
This function is necessary because navigation and control key
events do not seem to normally be seen by the wxComboBox's
event.Skip() # let mixin default KeyDown behavior occur
- def OnSelectChoice(self, event):
+ def _OnSelectChoice(self, event):
"""
This function appears to be necessary, because the processing done
on the text of the control somehow interferes with the combobox's
self._CheckValid()
## dbg('field._autoCompleteIndex:', match_index)
## dbg('self.GetSelection():', self.GetSelection())
+ end = self._goEnd(getPosOnly=True)
+## dbg('scheduling set of end position to:', end)
+ # work around bug in wx 2.5
+ wx.CallAfter(self.SetInsertionPoint, 0)
+ wx.CallAfter(self.SetInsertionPoint, end)
## dbg(indent=0)
def _OnReturn(self, event):
"""
- For wxComboBox, it seems that if you hit return when the dropdown is
+ For wx.ComboBox, it seems that if you hit return when the dropdown is
dropped, the event that dismisses the dropdown will also blank the
- control, because of the implementation of wxComboBox. So here,
- we look and if the selection is -1, and the value according to
- (the base control!) is a value in the list, then we schedule a
+ control, because of the implementation of wxComboBox. So this function
+ examines the selection and if it is -1, and the value according to
+ (the base control!) is a value in the list, then it schedules a
programmatic wxComboBox.SetSelection() call to pick the appropriate
- item in the list. (and then do the usual OnReturn bit.)
+ item in the list. (and then does the usual OnReturn bit.)
"""
## dbg('MaskedComboBox::OnReturn', indent=1)
## dbg('current value: "%s"' % self.GetValue(), 'current index:', self.GetSelection())
class ComboBox( BaseMaskedComboBox, MaskedEditAccessorsMixin ):
"""
- This extra level of inheritance allows us to add the generic set of
- masked edit parameters only to this class while allowing other
- classes to derive from the "base" masked combobox control, and provide
- a smaller set of valid accessor functions.
+ The "user-visible" masked combobox control, this class is
+ identical to the BaseMaskedComboBox class it's derived from.
+ (This extra level of inheritance allows us to add the generic
+ set of masked edit parameters only to this class while allowing
+ other classes to derive from the "base" masked combobox control,
+ and provide a smaller set of valid accessor functions.)
+ See BaseMaskedComboBox for available methods.
"""
pass
+class PreMaskedComboBox( BaseMaskedComboBox, MaskedEditAccessorsMixin ):
+ """
+ This class exists to support the use of XRC subclassing.
+ """
+ # This should really be wx.EVT_WINDOW_CREATE but it is not
+ # currently delivered for native controls on all platforms, so
+ # we'll use EVT_SIZE instead. It should happen shortly after the
+ # control is created as the control is set to its "best" size.
+ _firstEventType = wx.EVT_SIZE
+
+ def __init__(self):
+ pre = wx.PreComboBox()
+ self.PostCreate(pre)
+ self.Bind(self._firstEventType, self.OnCreate)
+
+
+ def OnCreate(self, evt):
+ self.Unbind(self._firstEventType)
+ self._PostInit()
+
+__i = 0
+## CHANGELOG:
+## ====================
+## Version 1.3
+## 1. Made definition of "hack" GetMark conditional on base class not
+## implementing it properly, to allow for migration in wx code base
+## while taking advantage of improvements therein for some platforms.
+##
+## Version 1.2
+## 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.1
+## 1. Added .SetFont() method that properly resizes control
+## 2. Modified control to support construction via XRC mechanism.
+## 3. Added AppendItems() to conform with latest combobox.