]> git.saurik.com Git - wxWidgets.git/blobdiff - wxPython/wx/lib/masked/combobox.py
Use MustHaveApp for wx.RendererNative.Get and others
[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
 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, 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)
 
     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 ):
     """
 
 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,
     """
     def __init__( self, parent, id=-1, value = '',
                   pos = wx.DefaultPosition,
@@ -54,11 +65,6 @@ class BaseMaskedComboBox( wx.ComboBox, MaskedEditMixin ):
                   **kwargs):
 
 
                   **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
         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)
                             choices=choices, style=style|wx.WANTS_CHARS,
                             validator=validator,
                             name=name)
-
         self.controlInitialized = True
 
         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())
         # 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:
 
         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._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
 
         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_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
             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])
 
 
         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.
         """
     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):
         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.
         """
         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.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):
 
     def _SetInsertionPoint(self, pos):
+##        dbg('MaskedComboBox::_SetInsertionPoint(%d)' % pos)
         self.SetInsertionPoint(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:
 
         # 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)
             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)
                 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):
 
 
     def _Refresh(self):
@@ -305,14 +374,13 @@ class BaseMaskedComboBox( wx.ComboBox, MaskedEditMixin ):
         else:
             wx.ComboBox.Undo()       # else revert to base control behavior
 
         else:
             wx.ComboBox.Undo()       # else revert to base control behavior
 
-
     def Append( self, choice, clientData=None ):
         """
     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):
         """
         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)
 
 
         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 ):
         """
 
     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 = []
         """
         if self._mask:
             self._choices = []
@@ -367,8 +443,8 @@ class BaseMaskedComboBox( wx.ComboBox, MaskedEditMixin ):
 
     def _OnCtrlParametersChanged(self):
         """
 
     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)
         """
         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 )
 
 
                 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):
         """
 
 
     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)
         current.
         """
 ##        dbg('MaskedComboBox::SetSelection(%d)' % index)
@@ -427,7 +513,7 @@ class BaseMaskedComboBox( wx.ComboBox, MaskedEditMixin ):
         wx.ComboBox.SetSelection(self, 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
         """
         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
 
 
             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
         """
         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())
         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):
         """
 ##        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
         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
         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())
         """
 ##        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 ):
     """
 
 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
 
 
     """
     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.