]> git.saurik.com Git - wxWidgets.git/blobdiff - wxPython/wx/lib/masked/combobox.py
Send the calendar and date changed events to the datepicker ctrl first
[wxWidgets.git] / wxPython / wx / lib / masked / combobox.py
index 8a78f9b651e6c8d10b8b67b2c7bf50e1691ec27c..1eba82de0c78469f759b91897d9b7ecfc8baccf6 100644 (file)
 #
 #----------------------------------------------------------------------------
 
-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)
 
@@ -37,11 +47,36 @@ class MaskedComboBoxSelectEvent(wx.PyCommandEvent):
         this event was generated."""
         return self.__selection
 
+class MaskedComboBoxEventHandler(wx.EvtHandler):
+    """
+    This handler ensures that the derived control can react to events
+    from the base control before any external handlers run, to ensure 
+    proper behavior.
+    """
+    def __init__(self, combobox):
+        wx.EvtHandler.__init__(self)
+        self.combobox = combobox
+        combobox.PushEventHandler(self)
+        self.Bind(wx.EVT_SET_FOCUS, self.combobox._OnFocus )            ## defeat automatic full selection
+        self.Bind(wx.EVT_KILL_FOCUS, self.combobox._OnKillFocus )       ## run internal validator
+        self.Bind(wx.EVT_LEFT_DCLICK, self.combobox._OnDoubleClick)     ## select field under cursor on dclick
+        self.Bind(wx.EVT_RIGHT_UP, self.combobox._OnContextMenu )       ## bring up an appropriate context menu
+        self.Bind(wx.EVT_CHAR, self.combobox._OnChar )                  ## handle each keypress
+        self.Bind(wx.EVT_KEY_DOWN, self.combobox._OnKeyDownInComboBox ) ## for special processing of up/down keys
+        self.Bind(wx.EVT_KEY_DOWN, self.combobox._OnKeyDown )           ## for processing the rest of the control keys
+                                                                        ## (next in evt chain)
+        self.Bind(wx.EVT_COMBOBOX, self.combobox._OnDropdownSelect )    ## to bring otherwise completely independent base
+                                                                        ## ctrl selection into maskededit framework
+        self.Bind(wx.EVT_TEXT, self.combobox._OnTextChange )            ## color control appropriately & keep
+                                                                        ## track of previous value for undo
+
+
 
 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,
@@ -54,11 +89,6 @@ class BaseMaskedComboBox( wx.ComboBox, MaskedEditMixin ):
                   **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
@@ -80,15 +110,55 @@ class BaseMaskedComboBox( wx.ComboBox, MaskedEditMixin ):
                             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())
-            self.SetSizeHints(self.GetSize())
+            width = self.GetSize().width
+            height = self.GetBestSize().height
+            self.SetInitialSize((width, height))
+
 
         if value:
             # ensure value is width of the mask of the control:
@@ -103,21 +173,18 @@ class BaseMaskedComboBox( wx.ComboBox, MaskedEditMixin ):
             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)
+
+        self.replace_next_combobox_event = False
+        self.correct_selection = -1
 
         if setupEventHandling:
-            ## Setup event handlers
-            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_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._OnKeyDown )        ## for processing the rest of the control keys
-                                                                ## (next in evt chain)
-            self.Bind(wx.EVT_TEXT, self._OnTextChange )         ## color control appropriately & keep
-                                                                ## track of previous value for undo
+            ## Setup event handling functions through event handler object,
+            ## to guarantee processing prior to giving event callbacks from
+            ## outside the class:
+            self.evt_handler = MaskedComboBoxEventHandler(self)
+            self.Bind(wx.EVT_WINDOW_DESTROY, self.OnWindowDestroy )
 
 
 
@@ -125,6 +192,13 @@ class BaseMaskedComboBox( wx.ComboBox, MaskedEditMixin ):
         return "<MaskedComboBox: %s>" % self.GetValue()
 
 
+    def OnWindowDestroy(self, event):
+        # clean up associated event handler object:
+        if self.RemoveEventHandler(self.evt_handler):
+            self.evt_handler.Destroy()
+        event.Skip()
+
+
     def _CalcSize(self, size=None):
         """
         Calculate automatic size if allowed; augment base mixin function
@@ -134,11 +208,24 @@ class BaseMaskedComboBox( wx.ComboBox, MaskedEditMixin ):
         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.SetInitialSize((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):
@@ -146,16 +233,29 @@ class BaseMaskedComboBox( wx.ComboBox, MaskedEditMixin ):
         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)
 
 
+    def IsEmpty(*args, **kw):
+        return MaskedEditMixin.IsEmpty(*args, **kw)
+
+
     def _GetValue(self):
         """
         Allow mixin to get the raw value of the control with this function.
@@ -180,7 +280,9 @@ class BaseMaskedComboBox( wx.ComboBox, MaskedEditMixin ):
         # Record current selection and insertion point, for undo
         self._prevSelection = self._GetSelection()
         self._prevInsertionPoint = self._GetInsertionPoint()
+##        dbg('MaskedComboBox::_SetValue(%s), selection beforehand: %d' % (value, self.GetSelection()))
         wx.ComboBox.SetValue(self, value)
+##        dbg('MaskedComboBox::_SetValue(%s), selection now: %d' % (value, self.GetSelection()))
         # text change events don't always fire, so we check validity here
         # to make certain formatting is applied:
         self._CheckValid()
@@ -192,11 +294,14 @@ class BaseMaskedComboBox( wx.ComboBox, MaskedEditMixin ):
         masked control.  NOTE: this must be done in the class derived
         from the base wx control.
         """
+##        dbg('MaskedComboBox::SetValue(%s)' % value, indent=1)
         if not self._mask:
             wx.ComboBox.SetValue(value)   # revert to base control behavior
+##            dbg('no mask; deferring to base class', indent=0)
             return
         # else...
         # empty previous contents, replacing entire value:
+##        dbg('MaskedComboBox::SetValue: selection beforehand: %d' % (self.GetSelection()))
         self._SetInsertionPoint(0)
         self._SetSelection(0, self._masklength)
 
@@ -219,7 +324,7 @@ class BaseMaskedComboBox( wx.ComboBox, MaskedEditMixin ):
 
         # 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)
@@ -234,15 +339,26 @@ class BaseMaskedComboBox( wx.ComboBox, MaskedEditMixin ):
                 dateparts = value.split(' ')
                 dateparts[0] = self._adjustDate(dateparts[0], fixcentury=True)
                 value = string.join(dateparts, ' ')
-##                dbg('adjusted value: "%s"' % value)
                 value = self._Paste(value, raise_on_invalid=True, just_return_value=True)
             else:
                 raise
+##        dbg('adjusted value: "%s"' % value)
 
-        self._SetValue(value)
-####        dbg('queuing insertion after .SetValue', self._masklength)
-        wx.CallAfter(self._SetInsertionPoint, self._masklength)
-        wx.CallAfter(self._SetSelection, self._masklength, self._masklength)
+        # Attempt to compensate for fact that calling .SetInsertionPoint() makes the
+        # selection index -1, even if the resulting set value is in the list.  
+        # So, if we are setting a value that's in the list, use index selection instead.
+        if value in self._choices:
+            index = self._choices.index(value)
+            self._prevValue = self._curValue
+            self._curValue = self._choices[index]
+            self._ctrl_constraints._autoCompleteIndex = index
+            self.SetSelection(index)
+        else:
+            self._SetValue(value)
+####            dbg('queuing insertion after .SetValue', replace_to)
+            wx.CallAfter(self._SetInsertionPoint, replace_to)
+            wx.CallAfter(self._SetSelection, replace_to, replace_to)
+##        dbg(indent=0)
 
 
     def _Refresh(self):
@@ -306,14 +422,13 @@ class BaseMaskedComboBox( wx.ComboBox, MaskedEditMixin ):
         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):
@@ -352,11 +467,19 @@ class BaseMaskedComboBox( wx.ComboBox, MaskedEditMixin ):
         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 = []
@@ -368,8 +491,8 @@ class BaseMaskedComboBox( wx.ComboBox, MaskedEditMixin ):
 
     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)
@@ -378,71 +501,115 @@ class BaseMaskedComboBox( wx.ComboBox, MaskedEditMixin ):
                 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)
+##        dbg('MaskedComboBox::SetSelection(%d)' % index, indent=1)
         if self._mask:
             self._prevValue = self._curValue
-            self._curValue = self._choices[index]
             self._ctrl_constraints._autoCompleteIndex = index
+            if index != -1:
+                self._curValue = self._choices[index]
+            else:
+                self._curValue = None
         wx.ComboBox.SetSelection(self, index)
+##        dbg('selection now: %d' % self.GetCurrentSelection(), indent=0)
 
 
-    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
-        EVT_CHAR routine.  (Tabs don't seem to be visible no matter
-        what... {:-( )
+        This function is necessary because navigation and control key events
+        do not seem to normally be seen by the wxComboBox's EVT_CHAR routine.
+        (Tabs don't seem to be visible no matter what, except for CB_READONLY
+        controls, for some bizarre reason... {:-( )
         """
+        key = event.GetKeyCode()
+##        dbg('MaskedComboBox::OnKeyDownInComboBox(%d)' % key)
         if event.GetKeyCode() in self._nav + self._control:
-            self._OnChar(event)
-            return
+            if not self._IsEditable():
+                # WANTS_CHARS with CB_READONLY apparently prevents navigation on WXK_TAB;
+                # ensure we can still navigate properly, as maskededit mixin::OnChar assumes
+                # that event.Skip() will just work, but it doesn't:
+                if self._keyhandlers.has_key(key):
+                    self._keyhandlers[key](event)
+                # else pass
+            else:
+##                dbg('calling OnChar()')
+                self._OnChar(event)
         else:
             event.Skip()    # let mixin default KeyDown behavior occur
+##        dbg(indent=0)
+
+
+    def _OnDropdownSelect(self, event):
+        """
+        This function appears to be necessary because dropdown selection seems to
+        manipulate the contents of the control in an inconsistent way, properly
+        changing the selection index, but *not* the value. (!)  Calling SetSelection()
+        on a selection event for the same selection would seem like a nop, but it seems to
+        fix the problem.
+        """
+##        dbg('MaskedComboBox::OnDropdownSelect(%d)' % event.GetSelection(), indent=1)
+        if self.replace_next_combobox_event:
+##            dbg('replacing EVT_COMBOBOX')
+            self.replace_next_combobox_event = False
+            self._OnAutoSelect(self._ctrl_constraints, self.correct_selection)
+        else:
+##            dbg('skipping EVT_COMBOBOX')
+            event.Skip()
+##        dbg(indent=0)
 
 
-    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
@@ -496,7 +663,7 @@ class BaseMaskedComboBox( wx.ComboBox, MaskedEditMixin ):
         Override mixin (empty) autocomplete handler, so that autocompletion causes
         combobox to update appropriately.
         """
-##        dbg('MaskedComboBox::OnAutoSelect', field._index, indent=1)
+##        dbg('MaskedComboBox::OnAutoSelect(%d, %d)' % (field._index, match_index), indent=1)
 ##        field._autoCompleteIndex = match_index
         if field == self._ctrl_constraints:
             self.SetSelection(match_index)
@@ -505,37 +672,104 @@ class BaseMaskedComboBox( wx.ComboBox, MaskedEditMixin ):
                 MaskedComboBoxSelectEvent( self.GetId(), match_index, self ) )
         self._CheckValid()
 ##        dbg('field._autoCompleteIndex:', match_index)
-##        dbg('self.GetSelection():', self.GetSelection())
+##        dbg('self.GetCurrentSelection():', self.GetCurrentSelection())
+        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())
-        if self.GetSelection() == -1 and self.GetValue().lower().strip() in self._ctrl_constraints._compareChoices:
-            wx.CallAfter(self.SetSelection, self._ctrl_constraints._autoCompleteIndex)
-
+##        dbg('current value: "%s"' % self.GetValue(), 'current selection:', self.GetCurrentSelection())
+        if self.GetCurrentSelection() == -1 and self.GetValue().lower().strip() in self._ctrl_constraints._compareChoices:
+##            dbg('attempting to correct the selection to make it %d' % self._ctrl_constraints._autoCompleteIndex)
+##            wx.CallAfter(self.SetSelection, self._ctrl_constraints._autoCompleteIndex)
+            self.replace_next_combobox_event = True
+            self.correct_selection = self._ctrl_constraints._autoCompleteIndex
         event.m_keyCode = wx.WXK_TAB
         event.Skip()
 ##        dbg(indent=0)
 
 
+    def _LostFocus(self):
+##        dbg('MaskedComboBox::LostFocus; Selection=%d, value="%s"' % (self.GetSelection(), self.GetValue()))
+        if self.GetCurrentSelection() == -1 and self.GetValue().lower().strip() in self._ctrl_constraints._compareChoices:
+##            dbg('attempting to correct the selection to make it %d' % self._ctrl_constraints._autoCompleteIndex)
+            wx.CallAfter(self.SetSelection, self._ctrl_constraints._autoCompleteIndex)
+
+
 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.4
+##  1. Added handler for EVT_COMBOBOX to address apparently inconsistent behavior
+##     of control when the dropdown control is used to do a selection.
+##     NOTE: due to misbehavior of wx.ComboBox re: losing all concept of the 
+##      current selection index if SetInsertionPoint() is called, which is required
+##      to support masked .SetValue(), this control is flaky about retaining selection
+##      information.  I can't truly fix this without major changes to the base control,
+##      but I've tried to compensate as best I can.
+##     TODO: investigate replacing base control with ComboCtrl instead...
+##  2. Fixed navigation in readonly masked combobox, which was not working because
+##      the base control doesn't do navigation if style=CB_READONLY|WANTS_CHARS.
+##
+##
+##  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.