]> git.saurik.com Git - wxWidgets.git/blobdiff - wxPython/wx/lib/masked/combobox.py
docstring update
[wxWidgets.git] / wxPython / wx / lib / masked / combobox.py
index 3619c398f2211df5b0e93f1bba740d0a526cb4dc..7c08ed42656d3f10299bc1551592715e9da8e917 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)
 
@@ -40,8 +50,9 @@ class MaskedComboBoxSelectEvent(wx.PyCommandEvent):
 
 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 +65,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,14 +86,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())
+            width = self.GetSize().width
+            height = self.GetBestSize().height
+            self.SetBestFittingSize((width, height))
+
 
         if value:
             # ensure value is width of the mask of the control:
@@ -102,8 +149,8 @@ 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)
 
         if setupEventHandling:
             ## Setup event handlers
@@ -112,7 +159,7 @@ class BaseMaskedComboBox( wx.ComboBox, MaskedEditMixin ):
             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
@@ -133,11 +180,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.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):
@@ -145,13 +205,22 @@ 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)
 
 
@@ -218,7 +287,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)
@@ -239,9 +308,9 @@ class BaseMaskedComboBox( wx.ComboBox, MaskedEditMixin ):
                 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):
@@ -305,14 +374,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):
@@ -351,11 +419,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 = []
@@ -367,8 +443,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)
@@ -377,46 +453,56 @@ 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)
@@ -427,7 +513,7 @@ class BaseMaskedComboBox( wx.ComboBox, MaskedEditMixin ):
         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
@@ -441,7 +527,7 @@ class BaseMaskedComboBox( wx.ComboBox, MaskedEditMixin ):
             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
@@ -505,18 +591,23 @@ class BaseMaskedComboBox( wx.ComboBox, MaskedEditMixin ):
         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())
@@ -530,11 +621,51 @@ class BaseMaskedComboBox( wx.ComboBox, MaskedEditMixin ):
 
 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.