1 #---------------------------------------------------------------------------- 
   2 # Name:         masked.combobox.py 
   4 # Email:        wsadkin@nameconnector.com 
   6 # Copyright:    (c) 2003 by Will Sadkin, 2003 
   8 # License:      wxWidgets license 
   9 #---------------------------------------------------------------------------- 
  11 # This masked edit class allows for the semantics of masked controls 
  12 # to be applied to combo boxes. 
  14 #---------------------------------------------------------------------------- 
  17 Provides masked edit capabilities within a ComboBox format, as well as 
  18 a base class from which you can derive masked comboboxes tailored to a specific 
  19 function.  See maskededit module overview for how to configure the control. 
  22 import  wx
, types
, string
 
  23 from wx
.lib
.masked 
import * 
  25 # jmg 12/9/03 - when we cut ties with Py 2.2 and earlier, this would 
  26 # be a good place to implement the 2.3 logger class 
  27 from wx
.tools
.dbg 
import Logger
 
  31 ## ---------- ---------- ---------- ---------- ---------- ---------- ---------- 
  32 ## Because calling SetSelection programmatically does not fire EVT_COMBOBOX 
  33 ## events, we have to do it ourselves when we auto-complete. 
  34 class MaskedComboBoxSelectEvent(wx
.PyCommandEvent
): 
  36     Because calling SetSelection programmatically does not fire EVT_COMBOBOX 
  37     events, the derived control has to do it itself when it auto-completes. 
  39     def __init__(self
, id, selection 
= 0, object=None): 
  40         wx
.PyCommandEvent
.__init
__(self
, wx
.wxEVT_COMMAND_COMBOBOX_SELECTED
, id) 
  42         self
.__selection 
= selection
 
  43         self
.SetEventObject(object) 
  45     def GetSelection(self
): 
  46         """Retrieve the value of the control at the time 
  47         this event was generated.""" 
  48         return self
.__selection
 
  51 class BaseMaskedComboBox( wx
.ComboBox
, MaskedEditMixin 
): 
  53     Base class for generic masked edit comboboxes; allows auto-complete of values. 
  54     It is not meant to be instantiated directly, but rather serves as a base class 
  55     for any subsequent refinements. 
  57     def __init__( self
, parent
, id=-1, value 
= '', 
  58                   pos 
= wx
.DefaultPosition
, 
  59                   size 
= wx
.DefaultSize
, 
  61                   style 
= wx
.CB_DROPDOWN
, 
  62                   validator 
= wx
.DefaultValidator
, 
  63                   name 
= "maskedComboBox", 
  64                   setupEventHandling 
= True,        ## setup event handling by default): 
  68         kwargs
['choices'] = choices                 
## set up maskededit to work with choice list too 
  70         ## Since combobox completion is case-insensitive, always validate same way 
  71         if not kwargs
.has_key('compareNoCase'): 
  72             kwargs
['compareNoCase'] = True 
  74         MaskedEditMixin
.__init
__( self
, name
, **kwargs 
) 
  76         self
._choices 
= self
._ctrl
_constraints
._choices
 
  77 ##        dbg('self._choices:', self._choices) 
  79         if self
._ctrl
_constraints
._alignRight
: 
  80             choices 
= [choice
.rjust(self
._masklength
) for choice 
in choices
] 
  82             choices 
= [choice
.ljust(self
._masklength
) for choice 
in choices
] 
  84         wx
.ComboBox
.__init
__(self
, parent
, id, value
='', 
  86                             choices
=choices
, style
=style|wx
.WANTS_CHARS
, 
  89         self
.controlInitialized 
= True 
  91         self
._PostInit
(style
=style
, setupEventHandling
=setupEventHandling
, 
  92                        name
=name
, value
=value
, **kwargs
) 
  95     def _PostInit(self
, style
=wx
.CB_DROPDOWN
, 
  96                   setupEventHandling 
= True,        ## setup event handling by default): 
  97                   name 
= "maskedComboBox", value
='', **kwargs
): 
  99         # This is necessary, because wxComboBox currently provides no 
 100         # method for determining later if this was specified in the 
 101         # constructor for the control... 
 102         self
.__readonly 
= style 
& wx
.CB_READONLY 
== wx
.CB_READONLY
 
 104         if not hasattr(self
, 'controlInitialized'): 
 106             self
.controlInitialized 
= True          ## must have been called via XRC, therefore base class is constructed 
 107             if not kwargs
.has_key('choices'): 
 109                 kwargs
['choices'] = choices         
## set up maskededit to work with choice list too 
 112             ## Since combobox completion is case-insensitive, always validate same way 
 113             if not kwargs
.has_key('compareNoCase'): 
 114                 kwargs
['compareNoCase'] = True 
 116             MaskedEditMixin
.__init
__( self
, name
, **kwargs 
) 
 118             self
._choices 
= self
._ctrl
_constraints
._choices
 
 119 ##        dbg('self._choices:', self._choices) 
 121             if self
._ctrl
_constraints
._alignRight
: 
 122                 choices 
= [choice
.rjust(self
._masklength
) for choice 
in choices
] 
 124                 choices 
= [choice
.ljust(self
._masklength
) for choice 
in choices
] 
 125             wx
.ComboBox
.Clear(self
) 
 126             wx
.ComboBox
.AppendItems(self
, choices
) 
 129         # Set control font - fixed width by default 
 133             self
.SetClientSize(self
._CalcSize
()) 
 134             width 
= self
.GetSize().width
 
 135             height 
= self
.GetBestSize().height
 
 136             self
.SetBestFittingSize((width
, height
)) 
 140             # ensure value is width of the mask of the control: 
 141             if self
._ctrl
_constraints
._alignRight
: 
 142                 value 
= value
.rjust(self
._masklength
) 
 144                 value 
= value
.ljust(self
._masklength
) 
 147             self
.SetStringSelection(value
) 
 149             self
._SetInitialValue
(value
) 
 152         self
._SetKeycodeHandler
(wx
.WXK_UP
, self
._OnSelectChoice
) 
 153         self
._SetKeycodeHandler
(wx
.WXK_DOWN
, self
._OnSelectChoice
) 
 155         if setupEventHandling
: 
 156             ## Setup event handlers 
 157             self
.Bind(wx
.EVT_SET_FOCUS
, self
._OnFocus 
)         ## defeat automatic full selection 
 158             self
.Bind(wx
.EVT_KILL_FOCUS
, self
._OnKillFocus 
)    ## run internal validator 
 159             self
.Bind(wx
.EVT_LEFT_DCLICK
, self
._OnDoubleClick
)  ## select field under cursor on dclick 
 160             self
.Bind(wx
.EVT_RIGHT_UP
, self
._OnContextMenu 
)    ## bring up an appropriate context menu 
 161             self
.Bind(wx
.EVT_CHAR
, self
._OnChar 
)               ## handle each keypress 
 162             self
.Bind(wx
.EVT_KEY_DOWN
, self
._OnKeyDownInComboBox 
) ## for special processing of up/down keys 
 163             self
.Bind(wx
.EVT_KEY_DOWN
, self
._OnKeyDown 
)        ## for processing the rest of the control keys 
 164                                                                 ## (next in evt chain) 
 165             self
.Bind(wx
.EVT_TEXT
, self
._OnTextChange 
)         ## color control appropriately & keep 
 166                                                                 ## track of previous value for undo 
 171         return "<MaskedComboBox: %s>" % self
.GetValue() 
 174     def _CalcSize(self
, size
=None): 
 176         Calculate automatic size if allowed; augment base mixin function 
 177         to account for the selector button. 
 179         size 
= self
._calcSize
(size
) 
 180         return (size
[0]+20, size
[1]) 
 183     def SetFont(self
, *args
, **kwargs
): 
 184         """ Set the font, then recalculate control size, if appropriate. """ 
 185         wx
.ComboBox
.SetFont(self
, *args
, **kwargs
) 
 187 ##            dbg('calculated size:', self._CalcSize())             
 188             self
.SetClientSize(self
._CalcSize
()) 
 189             width 
= self
.GetSize().width
 
 190             height 
= self
.GetBestSize().height
 
 191 ##            dbg('setting client size to:', (width, height)) 
 192             self
.SetBestFittingSize((width
, height
)) 
 195     def _GetSelection(self
): 
 197         Allow mixin to get the text selection of this control. 
 198         REQUIRED by any class derived from MaskedEditMixin. 
 200         return self
.GetMark() 
 202     def _SetSelection(self
, sel_start
, sel_to
): 
 204         Allow mixin to set the text selection of this control. 
 205         REQUIRED by any class derived from MaskedEditMixin. 
 207         return self
.SetMark( sel_start
, sel_to 
) 
 210     def _GetInsertionPoint(self
): 
 211         return self
.GetInsertionPoint() 
 213     def _SetInsertionPoint(self
, pos
): 
 214         self
.SetInsertionPoint(pos
) 
 219         Allow mixin to get the raw value of the control with this function. 
 220         REQUIRED by any class derived from MaskedEditMixin. 
 222         return self
.GetValue() 
 224     def _SetValue(self
, value
): 
 226         Allow mixin to set the raw value of the control with this function. 
 227         REQUIRED by any class derived from MaskedEditMixin. 
 229         # For wxComboBox, ensure that values are properly padded so that 
 230         # if varying length choices are supplied, they always show up 
 231         # in the window properly, and will be the appropriate length 
 233         if self
._ctrl
_constraints
._alignRight
: 
 234             value 
= value
.rjust(self
._masklength
) 
 236             value 
= value
.ljust(self
._masklength
) 
 238         # Record current selection and insertion point, for undo 
 239         self
._prevSelection 
= self
._GetSelection
() 
 240         self
._prevInsertionPoint 
= self
._GetInsertionPoint
() 
 241         wx
.ComboBox
.SetValue(self
, value
) 
 242         # text change events don't always fire, so we check validity here 
 243         # to make certain formatting is applied: 
 246     def SetValue(self
, value
): 
 248         This function redefines the externally accessible .SetValue to be 
 249         a smart "paste" of the text in question, so as not to corrupt the 
 250         masked control.  NOTE: this must be done in the class derived 
 251         from the base wx control. 
 254             wx
.ComboBox
.SetValue(value
)   # revert to base control behavior 
 257         # empty previous contents, replacing entire value: 
 258         self
._SetInsertionPoint
(0) 
 259         self
._SetSelection
(0, self
._masklength
) 
 261         if( len(value
) < self
._masklength                
# value shorter than control 
 262             and (self
._isFloat 
or self
._isInt
)            # and it's a numeric control 
 263             and self
._ctrl
_constraints
._alignRight 
):   # and it's a right-aligned control 
 264             # try to intelligently "pad out" the value to the right size: 
 265             value 
= self
._template
[0:self
._masklength 
- len(value
)] + value
 
 266 ##            dbg('padded value = "%s"' % value) 
 268         # For wxComboBox, ensure that values are properly padded so that 
 269         # if varying length choices are supplied, they always show up 
 270         # in the window properly, and will be the appropriate length 
 272         elif self
._ctrl
_constraints
._alignRight
: 
 273             value 
= value
.rjust(self
._masklength
) 
 275             value 
= value
.ljust(self
._masklength
) 
 278         # make SetValue behave the same as if you had typed the value in: 
 280             value
, replace_to 
= self
._Paste
(value
, raise_on_invalid
=True, just_return_value
=True) 
 282                 self
._isNeg 
= False     # (clear current assumptions) 
 283                 value 
= self
._adjustFloat
(value
) 
 285                 self
._isNeg 
= False     # (clear current assumptions) 
 286                 value 
= self
._adjustInt
(value
) 
 287             elif self
._isDate 
and not self
.IsValid(value
) and self
._4digityear
: 
 288                 value 
= self
._adjustDate
(value
, fixcentury
=True) 
 290             # If date, year might be 2 digits vs. 4; try adjusting it: 
 291             if self
._isDate 
and self
._4digityear
: 
 292                 dateparts 
= value
.split(' ') 
 293                 dateparts
[0] = self
._adjustDate
(dateparts
[0], fixcentury
=True) 
 294                 value 
= string
.join(dateparts
, ' ') 
 295 ##                dbg('adjusted value: "%s"' % value) 
 296                 value 
= self
._Paste
(value
, raise_on_invalid
=True, just_return_value
=True) 
 300         self
._SetValue
(value
) 
 301 ####        dbg('queuing insertion after .SetValue', replace_to) 
 302         wx
.CallAfter(self
._SetInsertionPoint
, replace_to
) 
 303         wx
.CallAfter(self
._SetSelection
, replace_to
, replace_to
) 
 308         Allow mixin to refresh the base control with this function. 
 309         REQUIRED by any class derived from MaskedEditMixin. 
 311         wx
.ComboBox
.Refresh(self
) 
 315         This function redefines the externally accessible .Refresh() to 
 316         validate the contents of the masked control as it refreshes. 
 317         NOTE: this must be done in the class derived from the base wx control. 
 323     def _IsEditable(self
): 
 325         Allow mixin to determine if the base control is editable with this function. 
 326         REQUIRED by any class derived from MaskedEditMixin. 
 328         return not self
.__readonly
 
 333         This function redefines the externally accessible .Cut to be 
 334         a smart "erase" of the text in question, so as not to corrupt the 
 335         masked control.  NOTE: this must be done in the class derived 
 336         from the base wx control. 
 339             self
._Cut
()             # call the mixin's Cut method 
 341             wx
.ComboBox
.Cut(self
)    # else revert to base control behavior 
 346         This function redefines the externally accessible .Paste to be 
 347         a smart "paste" of the text in question, so as not to corrupt the 
 348         masked control.  NOTE: this must be done in the class derived 
 349         from the base wx control. 
 352             self
._Paste
()           # call the mixin's Paste method 
 354             wx
.ComboBox
.Paste(self
)  # else revert to base control behavior 
 359         This function defines the undo operation for the control. (The default 
 365             wx
.ComboBox
.Undo()       # else revert to base control behavior 
 367     def Append( self
, choice
, clientData
=None ): 
 369         This base control function override is necessary so the control can keep track 
 370         of any additions to the list of choices, because wx.ComboBox doesn't have an 
 371         accessor for the choice list.  The code here is the same as in the 
 372         SetParameters() mixin function, but is done for the individual value 
 373         as appended, so the list can be built incrementally without speed penalty. 
 376             if type(choice
) not in (types
.StringType
, types
.UnicodeType
): 
 377                 raise TypeError('%s: choices must be a sequence of strings' % str(self
._index
)) 
 378             elif not self
.IsValid(choice
): 
 379                 raise ValueError('%s: "%s" is not a valid value for the control as specified.' % (str(self
._index
), choice
)) 
 381             if not self
._ctrl
_constraints
._choices
: 
 382                 self
._ctrl
_constraints
._compareChoices 
= [] 
 383                 self
._ctrl
_constraints
._choices 
= [] 
 386             compareChoice 
= choice
.strip() 
 388             if self
._ctrl
_constraints
._compareNoCase
: 
 389                 compareChoice 
= compareChoice
.lower() 
 391             if self
._ctrl
_constraints
._alignRight
: 
 392                 choice 
= choice
.rjust(self
._masklength
) 
 394                 choice 
= choice
.ljust(self
._masklength
) 
 395             if self
._ctrl
_constraints
._fillChar 
!= ' ': 
 396                 choice 
= choice
.replace(' ', self
._fillChar
) 
 397 ##            dbg('updated choice:', choice) 
 400             self
._ctrl
_constraints
._compareChoices
.append(compareChoice
) 
 401             self
._ctrl
_constraints
._choices
.append(choice
) 
 402             self
._choices 
= self
._ctrl
_constraints
._choices     
# (for shorthand) 
 404             if( not self
.IsValid(choice
) and 
 405                (not self
._ctrl
_constraints
.IsEmpty(choice
) or 
 406                 (self
._ctrl
_constraints
.IsEmpty(choice
) and self
._ctrl
_constraints
._validRequired
) ) ): 
 407                 raise ValueError('"%s" is not a valid value for the control "%s" as specified.' % (choice
, self
.name
)) 
 409         wx
.ComboBox
.Append(self
, choice
, clientData
) 
 412     def AppendItems( self
, choices 
): 
 414         AppendItems() is handled in terms of Append, to avoid code replication. 
 416         for choice 
in choices
: 
 422         This base control function override is necessary so the derived control can 
 423         keep track of any additions to the list of choices, because wx.ComboBox 
 424         doesn't have an accessor for the choice list. 
 428             self
._ctrl
_constraints
._autoCompleteIndex 
= -1 
 429             if self
._ctrl
_constraints
._choices
: 
 430                 self
.SetCtrlParameters(choices
=[]) 
 431         wx
.ComboBox
.Clear(self
) 
 434     def _OnCtrlParametersChanged(self
): 
 436         This overrides the mixin's default OnCtrlParametersChanged to detect 
 437         changes in choice list, so masked.Combobox can update the base control: 
 439         if self
.controlInitialized 
and self
._choices 
!= self
._ctrl
_constraints
._choices
: 
 440             wx
.ComboBox
.Clear(self
) 
 441             self
._choices 
= self
._ctrl
_constraints
._choices
 
 442             for choice 
in self
._choices
: 
 443                 wx
.ComboBox
.Append( self
, choice 
) 
 446     # Not all wx platform implementations have .GetMark, so we make the following test, 
 447     # and fall back to our old hack if they don't... 
 449     if not hasattr(wx
.ComboBox
, 'GetMark'): 
 452             This function is a hack to make up for the fact that wx.ComboBox has no 
 453             method for returning the selected portion of its edit control.  It 
 454             works, but has the nasty side effect of generating lots of intermediate 
 457 ##            dbg(suspend=1)  # turn off debugging around this function 
 458 ##            dbg('MaskedComboBox::GetMark', indent=1) 
 461                 return 0, 0 # no selection possible for editing 
 462 ##            sel_start, sel_to = wxComboBox.GetMark(self)        # what I'd *like* to have! 
 463             sel_start 
= sel_to 
= self
.GetInsertionPoint() 
 464 ##            dbg("current sel_start:", sel_start) 
 465             value 
= self
.GetValue() 
 466 ##            dbg('value: "%s"' % value) 
 468             self
._ignoreChange 
= True               # tell _OnTextChange() to ignore next event (if any) 
 470             wx
.ComboBox
.Cut(self
) 
 471             newvalue 
= self
.GetValue() 
 472 ##            dbg("value after Cut operation:", newvalue) 
 474             if newvalue 
!= value
:                   # something was selected; calculate extent 
 475 ##                dbg("something selected") 
 476                 sel_to 
= sel_start 
+ len(value
) - len(newvalue
) 
 477                 wx
.ComboBox
.SetValue(self
, value
)    # restore original value and selection (still ignoring change) 
 478                 wx
.ComboBox
.SetInsertionPoint(self
, sel_start
) 
 479                 wx
.ComboBox
.SetMark(self
, sel_start
, sel_to
) 
 481             self
._ignoreChange 
= False              # tell _OnTextChange() to pay attn again 
 483 ##            dbg('computed selection:', sel_start, sel_to, indent=0, suspend=0) 
 484             return sel_start
, sel_to
 
 487     def SetSelection(self
, index
): 
 489         Necessary override for bookkeeping on choice selection, to keep current value 
 492 ##        dbg('MaskedComboBox::SetSelection(%d)' % index) 
 494             self
._prevValue 
= self
._curValue
 
 495             self
._curValue 
= self
._choices
[index
] 
 496             self
._ctrl
_constraints
._autoCompleteIndex 
= index
 
 497         wx
.ComboBox
.SetSelection(self
, index
) 
 500     def _OnKeyDownInComboBox(self
, event
): 
 502         This function is necessary because navigation and control key 
 503         events do not seem to normally be seen by the wxComboBox's 
 504         EVT_CHAR routine.  (Tabs don't seem to be visible no matter 
 507         if event
.GetKeyCode() in self
._nav 
+ self
._control
: 
 511             event
.Skip()    # let mixin default KeyDown behavior occur 
 514     def _OnSelectChoice(self
, event
): 
 516         This function appears to be necessary, because the processing done 
 517         on the text of the control somehow interferes with the combobox's 
 518         selection mechanism for the arrow keys. 
 520 ##        dbg('MaskedComboBox::OnSelectChoice', indent=1) 
 526         value 
= self
.GetValue().strip() 
 528         if self
._ctrl
_constraints
._compareNoCase
: 
 529             value 
= value
.lower() 
 531         if event
.GetKeyCode() == wx
.WXK_UP
: 
 535         match_index
, partial_match 
= self
._autoComplete
( 
 537                                                 self
._ctrl
_constraints
._compareChoices
, 
 539                                                 self
._ctrl
_constraints
._compareNoCase
, 
 540                                                 current_index 
= self
._ctrl
_constraints
._autoCompleteIndex
) 
 541         if match_index 
is not None: 
 542 ##            dbg('setting selection to', match_index) 
 543             # issue appropriate event to outside: 
 544             self
._OnAutoSelect
(self
._ctrl
_constraints
, match_index
=match_index
) 
 546             keep_processing 
= False 
 548             pos 
= self
._adjustPos
(self
._GetInsertionPoint
(), event
.GetKeyCode()) 
 549             field 
= self
._FindField
(pos
) 
 550             if self
.IsEmpty() or not field
._hasList
: 
 551 ##                dbg('selecting 1st value in list') 
 552                 self
._OnAutoSelect
(self
._ctrl
_constraints
, match_index
=0) 
 554                 keep_processing 
= False 
 556                 # attempt field-level auto-complete 
 558                 keep_processing 
= self
._OnAutoCompleteField
(event
) 
 559 ##        dbg('keep processing?', keep_processing, indent=0) 
 560         return keep_processing
 
 563     def _OnAutoSelect(self
, field
, match_index
): 
 565         Override mixin (empty) autocomplete handler, so that autocompletion causes 
 566         combobox to update appropriately. 
 568 ##        dbg('MaskedComboBox::OnAutoSelect', field._index, indent=1) 
 569 ##        field._autoCompleteIndex = match_index 
 570         if field 
== self
._ctrl
_constraints
: 
 571             self
.SetSelection(match_index
) 
 572 ##            dbg('issuing combo selection event') 
 573             self
.GetEventHandler().ProcessEvent( 
 574                 MaskedComboBoxSelectEvent( self
.GetId(), match_index
, self 
) ) 
 576 ##        dbg('field._autoCompleteIndex:', match_index) 
 577 ##        dbg('self.GetSelection():', self.GetSelection()) 
 578         end 
= self
._goEnd
(getPosOnly
=True) 
 579 ##        dbg('scheduling set of end position to:', end) 
 580         # work around bug in wx 2.5 
 581         wx
.CallAfter(self
.SetInsertionPoint
, 0) 
 582         wx
.CallAfter(self
.SetInsertionPoint
, end
) 
 586     def _OnReturn(self
, event
): 
 588         For wx.ComboBox, it seems that if you hit return when the dropdown is 
 589         dropped, the event that dismisses the dropdown will also blank the 
 590         control, because of the implementation of wxComboBox.  So this function 
 591         examines the selection and if it is -1, and the value according to 
 592         (the base control!) is a value in the list, then it schedules a 
 593         programmatic wxComboBox.SetSelection() call to pick the appropriate 
 594         item in the list. (and then does the usual OnReturn bit.) 
 596 ##        dbg('MaskedComboBox::OnReturn', indent=1) 
 597 ##        dbg('current value: "%s"' % self.GetValue(), 'current index:', self.GetSelection()) 
 598         if self
.GetSelection() == -1 and self
.GetValue().lower().strip() in self
._ctrl
_constraints
._compareChoices
: 
 599             wx
.CallAfter(self
.SetSelection
, self
._ctrl
_constraints
._autoCompleteIndex
) 
 601         event
.m_keyCode 
= wx
.WXK_TAB
 
 606 class ComboBox( BaseMaskedComboBox
, MaskedEditAccessorsMixin 
): 
 608     The "user-visible" masked combobox control, this class is 
 609     identical to the BaseMaskedComboBox class it's derived from. 
 610     (This extra level of inheritance allows us to add the generic 
 611     set of masked edit parameters only to this class while allowing 
 612     other classes to derive from the "base" masked combobox control, 
 613     and provide a smaller set of valid accessor functions.) 
 614     See BaseMaskedComboBox for available methods. 
 619 class PreMaskedComboBox( BaseMaskedComboBox
, MaskedEditAccessorsMixin 
): 
 621     This class exists to support the use of XRC subclassing. 
 623     # This should really be wx.EVT_WINDOW_CREATE but it is not 
 624     # currently delivered for native controls on all platforms, so 
 625     # we'll use EVT_SIZE instead.  It should happen shortly after the 
 626     # control is created as the control is set to its "best" size. 
 627     _firstEventType 
= wx
.EVT_SIZE
 
 630         pre 
= wx
.PreComboBox() 
 632         self
.Bind(self
._firstEventType
, self
.OnCreate
) 
 635     def OnCreate(self
, evt
): 
 636         self
.Unbind(self
._firstEventType
) 
 641 ## ==================== 
 643 ##  1. Made definition of "hack" GetMark conditional on base class not 
 644 ##     implementing it properly, to allow for migration in wx code base 
 645 ##     while taking advantage of improvements therein for some platforms. 
 648 ##  1. Converted docstrings to reST format, added doc for ePyDoc. 
 649 ##  2. Renamed helper functions, vars etc. not intended to be visible in public 
 650 ##     interface to code. 
 653 ##  1. Added .SetFont() method that properly resizes control 
 654 ##  2. Modified control to support construction via XRC mechanism. 
 655 ##  3. Added AppendItems() to conform with latest combobox.