Patch from Will Sadkin
[wxWidgets.git] / wxPython / wx / lib / masked / combobox.py
1 #----------------------------------------------------------------------------
2 # Name: masked.combobox.py
3 # Authors: Will Sadkin
4 # Email: wsadkin@nameconnector.com
5 # Created: 02/11/2003
6 # Copyright: (c) 2003 by Will Sadkin, 2003
7 # RCS-ID: $Id$
8 # License: wxWidgets license
9 #----------------------------------------------------------------------------
10 #
11 # This masked edit class allows for the semantics of masked controls
12 # to be applied to combo boxes.
13 #
14 #----------------------------------------------------------------------------
15
16 """
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.
20 """
21
22 import wx, types, string
23 from wx.lib.masked import *
24
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
28 ##dbg = Logger()
29 ##dbg(enable=1)
30
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):
35 """
36 Because calling SetSelection programmatically does not fire EVT_COMBOBOX
37 events, the derived control has to do it itself when it auto-completes.
38 """
39 def __init__(self, id, selection = 0, object=None):
40 wx.PyCommandEvent.__init__(self, wx.wxEVT_COMMAND_COMBOBOX_SELECTED, id)
41
42 self.__selection = selection
43 self.SetEventObject(object)
44
45 def GetSelection(self):
46 """Retrieve the value of the control at the time
47 this event was generated."""
48 return self.__selection
49
50
51 class BaseMaskedComboBox( wx.ComboBox, MaskedEditMixin ):
52 """
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.
56 """
57 def __init__( self, parent, id=-1, value = '',
58 pos = wx.DefaultPosition,
59 size = wx.DefaultSize,
60 choices = [],
61 style = wx.CB_DROPDOWN,
62 validator = wx.DefaultValidator,
63 name = "maskedComboBox",
64 setupEventHandling = True, ## setup event handling by default):
65 **kwargs):
66
67
68 kwargs['choices'] = choices ## set up maskededit to work with choice list too
69
70 ## Since combobox completion is case-insensitive, always validate same way
71 if not kwargs.has_key('compareNoCase'):
72 kwargs['compareNoCase'] = True
73
74 MaskedEditMixin.__init__( self, name, **kwargs )
75
76 self._choices = self._ctrl_constraints._choices
77 ## dbg('self._choices:', self._choices)
78
79 if self._ctrl_constraints._alignRight:
80 choices = [choice.rjust(self._masklength) for choice in choices]
81 else:
82 choices = [choice.ljust(self._masklength) for choice in choices]
83
84 wx.ComboBox.__init__(self, parent, id, value='',
85 pos=pos, size = size,
86 choices=choices, style=style|wx.WANTS_CHARS,
87 validator=validator,
88 name=name)
89 self.controlInitialized = True
90
91 self._PostInit(style=style, setupEventHandling=setupEventHandling,
92 name=name, value=value, **kwargs)
93
94
95 def _PostInit(self, style=wx.CB_DROPDOWN,
96 setupEventHandling = True, ## setup event handling by default):
97 name = "maskedComboBox", value='', **kwargs):
98
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
103
104 if not hasattr(self, 'controlInitialized'):
105
106 self.controlInitialized = True ## must have been called via XRC, therefore base class is constructed
107 if not kwargs.has_key('choices'):
108 choices=[]
109 kwargs['choices'] = choices ## set up maskededit to work with choice list too
110 self._choices = []
111
112 ## Since combobox completion is case-insensitive, always validate same way
113 if not kwargs.has_key('compareNoCase'):
114 kwargs['compareNoCase'] = True
115
116 MaskedEditMixin.__init__( self, name, **kwargs )
117
118 self._choices = self._ctrl_constraints._choices
119 ## dbg('self._choices:', self._choices)
120
121 if self._ctrl_constraints._alignRight:
122 choices = [choice.rjust(self._masklength) for choice in choices]
123 else:
124 choices = [choice.ljust(self._masklength) for choice in choices]
125 wx.ComboBox.Clear(self)
126 wx.ComboBox.AppendItems(self, choices)
127
128
129 # Set control font - fixed width by default
130 self._setFont()
131
132 if self._autofit:
133 self.SetClientSize(self._CalcSize())
134 width = self.GetSize().width
135 height = self.GetBestSize().height
136 self.SetBestFittingSize((width, height))
137
138
139 if value:
140 # ensure value is width of the mask of the control:
141 if self._ctrl_constraints._alignRight:
142 value = value.rjust(self._masklength)
143 else:
144 value = value.ljust(self._masklength)
145
146 if self.__readonly:
147 self.SetStringSelection(value)
148 else:
149 self._SetInitialValue(value)
150
151
152 self._SetKeycodeHandler(wx.WXK_UP, self._OnSelectChoice)
153 self._SetKeycodeHandler(wx.WXK_DOWN, self._OnSelectChoice)
154
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
167
168
169
170 def __repr__(self):
171 return "<MaskedComboBox: %s>" % self.GetValue()
172
173
174 def _CalcSize(self, size=None):
175 """
176 Calculate automatic size if allowed; augment base mixin function
177 to account for the selector button.
178 """
179 size = self._calcSize(size)
180 return (size[0]+20, size[1])
181
182
183 def SetFont(self, *args, **kwargs):
184 """ Set the font, then recalculate control size, if appropriate. """
185 wx.ComboBox.SetFont(self, *args, **kwargs)
186 if self._autofit:
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))
193
194
195 def _GetSelection(self):
196 """
197 Allow mixin to get the text selection of this control.
198 REQUIRED by any class derived from MaskedEditMixin.
199 """
200 ## dbg('MaskedComboBox::_GetSelection()')
201 return self.GetMark()
202
203 def _SetSelection(self, sel_start, sel_to):
204 """
205 Allow mixin to set the text selection of this control.
206 REQUIRED by any class derived from MaskedEditMixin.
207 """
208 ## dbg('MaskedComboBox::_SetSelection: setting mark to (%d, %d)' % (sel_start, sel_to))
209 return self.SetMark( sel_start, sel_to )
210
211
212 def _GetInsertionPoint(self):
213 ## dbg('MaskedComboBox::_GetInsertionPoint()', indent=1)
214 ## ret = self.GetInsertionPoint()
215 # work around new bug in 2.5, in which the insertion point
216 # returned is always at the right side of the selection,
217 # rather than the start, as is the case with TextCtrl.
218 ret = self.GetMark()[0]
219 ## dbg('returned', ret, indent=0)
220 return ret
221
222 def _SetInsertionPoint(self, pos):
223 ## dbg('MaskedComboBox::_SetInsertionPoint(%d)' % pos)
224 self.SetInsertionPoint(pos)
225
226
227 def _GetValue(self):
228 """
229 Allow mixin to get the raw value of the control with this function.
230 REQUIRED by any class derived from MaskedEditMixin.
231 """
232 return self.GetValue()
233
234 def _SetValue(self, value):
235 """
236 Allow mixin to set the raw value of the control with this function.
237 REQUIRED by any class derived from MaskedEditMixin.
238 """
239 # For wxComboBox, ensure that values are properly padded so that
240 # if varying length choices are supplied, they always show up
241 # in the window properly, and will be the appropriate length
242 # to match the mask:
243 if self._ctrl_constraints._alignRight:
244 value = value.rjust(self._masklength)
245 else:
246 value = value.ljust(self._masklength)
247
248 # Record current selection and insertion point, for undo
249 self._prevSelection = self._GetSelection()
250 self._prevInsertionPoint = self._GetInsertionPoint()
251 wx.ComboBox.SetValue(self, value)
252 # text change events don't always fire, so we check validity here
253 # to make certain formatting is applied:
254 self._CheckValid()
255
256 def SetValue(self, value):
257 """
258 This function redefines the externally accessible .SetValue to be
259 a smart "paste" of the text in question, so as not to corrupt the
260 masked control. NOTE: this must be done in the class derived
261 from the base wx control.
262 """
263 if not self._mask:
264 wx.ComboBox.SetValue(value) # revert to base control behavior
265 return
266 # else...
267 # empty previous contents, replacing entire value:
268 self._SetInsertionPoint(0)
269 self._SetSelection(0, self._masklength)
270
271 if( len(value) < self._masklength # value shorter than control
272 and (self._isFloat or self._isInt) # and it's a numeric control
273 and self._ctrl_constraints._alignRight ): # and it's a right-aligned control
274 # try to intelligently "pad out" the value to the right size:
275 value = self._template[0:self._masklength - len(value)] + value
276 ## dbg('padded value = "%s"' % value)
277
278 # For wxComboBox, ensure that values are properly padded so that
279 # if varying length choices are supplied, they always show up
280 # in the window properly, and will be the appropriate length
281 # to match the mask:
282 elif self._ctrl_constraints._alignRight:
283 value = value.rjust(self._masklength)
284 else:
285 value = value.ljust(self._masklength)
286
287
288 # make SetValue behave the same as if you had typed the value in:
289 try:
290 value, replace_to = self._Paste(value, raise_on_invalid=True, just_return_value=True)
291 if self._isFloat:
292 self._isNeg = False # (clear current assumptions)
293 value = self._adjustFloat(value)
294 elif self._isInt:
295 self._isNeg = False # (clear current assumptions)
296 value = self._adjustInt(value)
297 elif self._isDate and not self.IsValid(value) and self._4digityear:
298 value = self._adjustDate(value, fixcentury=True)
299 except ValueError:
300 # If date, year might be 2 digits vs. 4; try adjusting it:
301 if self._isDate and self._4digityear:
302 dateparts = value.split(' ')
303 dateparts[0] = self._adjustDate(dateparts[0], fixcentury=True)
304 value = string.join(dateparts, ' ')
305 ## dbg('adjusted value: "%s"' % value)
306 value = self._Paste(value, raise_on_invalid=True, just_return_value=True)
307 else:
308 raise
309
310 self._SetValue(value)
311 #### dbg('queuing insertion after .SetValue', replace_to)
312 wx.CallAfter(self._SetInsertionPoint, replace_to)
313 wx.CallAfter(self._SetSelection, replace_to, replace_to)
314
315
316 def _Refresh(self):
317 """
318 Allow mixin to refresh the base control with this function.
319 REQUIRED by any class derived from MaskedEditMixin.
320 """
321 wx.ComboBox.Refresh(self)
322
323 def Refresh(self):
324 """
325 This function redefines the externally accessible .Refresh() to
326 validate the contents of the masked control as it refreshes.
327 NOTE: this must be done in the class derived from the base wx control.
328 """
329 self._CheckValid()
330 self._Refresh()
331
332
333 def _IsEditable(self):
334 """
335 Allow mixin to determine if the base control is editable with this function.
336 REQUIRED by any class derived from MaskedEditMixin.
337 """
338 return not self.__readonly
339
340
341 def Cut(self):
342 """
343 This function redefines the externally accessible .Cut to be
344 a smart "erase" of the text in question, so as not to corrupt the
345 masked control. NOTE: this must be done in the class derived
346 from the base wx control.
347 """
348 if self._mask:
349 self._Cut() # call the mixin's Cut method
350 else:
351 wx.ComboBox.Cut(self) # else revert to base control behavior
352
353
354 def Paste(self):
355 """
356 This function redefines the externally accessible .Paste to be
357 a smart "paste" of the text in question, so as not to corrupt the
358 masked control. NOTE: this must be done in the class derived
359 from the base wx control.
360 """
361 if self._mask:
362 self._Paste() # call the mixin's Paste method
363 else:
364 wx.ComboBox.Paste(self) # else revert to base control behavior
365
366
367 def Undo(self):
368 """
369 This function defines the undo operation for the control. (The default
370 undo is 1-deep.)
371 """
372 if self._mask:
373 self._Undo()
374 else:
375 wx.ComboBox.Undo() # else revert to base control behavior
376
377 def Append( self, choice, clientData=None ):
378 """
379 This base control function override is necessary so the control can keep track
380 of any additions to the list of choices, because wx.ComboBox doesn't have an
381 accessor for the choice list. The code here is the same as in the
382 SetParameters() mixin function, but is done for the individual value
383 as appended, so the list can be built incrementally without speed penalty.
384 """
385 if self._mask:
386 if type(choice) not in (types.StringType, types.UnicodeType):
387 raise TypeError('%s: choices must be a sequence of strings' % str(self._index))
388 elif not self.IsValid(choice):
389 raise ValueError('%s: "%s" is not a valid value for the control as specified.' % (str(self._index), choice))
390
391 if not self._ctrl_constraints._choices:
392 self._ctrl_constraints._compareChoices = []
393 self._ctrl_constraints._choices = []
394 self._hasList = True
395
396 compareChoice = choice.strip()
397
398 if self._ctrl_constraints._compareNoCase:
399 compareChoice = compareChoice.lower()
400
401 if self._ctrl_constraints._alignRight:
402 choice = choice.rjust(self._masklength)
403 else:
404 choice = choice.ljust(self._masklength)
405 if self._ctrl_constraints._fillChar != ' ':
406 choice = choice.replace(' ', self._fillChar)
407 ## dbg('updated choice:', choice)
408
409
410 self._ctrl_constraints._compareChoices.append(compareChoice)
411 self._ctrl_constraints._choices.append(choice)
412 self._choices = self._ctrl_constraints._choices # (for shorthand)
413
414 if( not self.IsValid(choice) and
415 (not self._ctrl_constraints.IsEmpty(choice) or
416 (self._ctrl_constraints.IsEmpty(choice) and self._ctrl_constraints._validRequired) ) ):
417 raise ValueError('"%s" is not a valid value for the control "%s" as specified.' % (choice, self.name))
418
419 wx.ComboBox.Append(self, choice, clientData)
420
421
422 def AppendItems( self, choices ):
423 """
424 AppendItems() is handled in terms of Append, to avoid code replication.
425 """
426 for choice in choices:
427 self.Append(choice)
428
429
430 def Clear( self ):
431 """
432 This base control function override is necessary so the derived control can
433 keep track of any additions to the list of choices, because wx.ComboBox
434 doesn't have an accessor for the choice list.
435 """
436 if self._mask:
437 self._choices = []
438 self._ctrl_constraints._autoCompleteIndex = -1
439 if self._ctrl_constraints._choices:
440 self.SetCtrlParameters(choices=[])
441 wx.ComboBox.Clear(self)
442
443
444 def _OnCtrlParametersChanged(self):
445 """
446 This overrides the mixin's default OnCtrlParametersChanged to detect
447 changes in choice list, so masked.Combobox can update the base control:
448 """
449 if self.controlInitialized and self._choices != self._ctrl_constraints._choices:
450 wx.ComboBox.Clear(self)
451 self._choices = self._ctrl_constraints._choices
452 for choice in self._choices:
453 wx.ComboBox.Append( self, choice )
454
455
456 # Not all wx platform implementations have .GetMark, so we make the following test,
457 # and fall back to our old hack if they don't...
458 #
459 if not hasattr(wx.ComboBox, 'GetMark'):
460 def GetMark(self):
461 """
462 This function is a hack to make up for the fact that wx.ComboBox has no
463 method for returning the selected portion of its edit control. It
464 works, but has the nasty side effect of generating lots of intermediate
465 events.
466 """
467 ## dbg(suspend=1) # turn off debugging around this function
468 ## dbg('MaskedComboBox::GetMark', indent=1)
469 if self.__readonly:
470 ## dbg(indent=0)
471 return 0, 0 # no selection possible for editing
472 ## sel_start, sel_to = wxComboBox.GetMark(self) # what I'd *like* to have!
473 sel_start = sel_to = self.GetInsertionPoint()
474 ## dbg("current sel_start:", sel_start)
475 value = self.GetValue()
476 ## dbg('value: "%s"' % value)
477
478 self._ignoreChange = True # tell _OnTextChange() to ignore next event (if any)
479
480 wx.ComboBox.Cut(self)
481 newvalue = self.GetValue()
482 ## dbg("value after Cut operation:", newvalue)
483
484 if newvalue != value: # something was selected; calculate extent
485 ## dbg("something selected")
486 sel_to = sel_start + len(value) - len(newvalue)
487 wx.ComboBox.SetValue(self, value) # restore original value and selection (still ignoring change)
488 wx.ComboBox.SetInsertionPoint(self, sel_start)
489 wx.ComboBox.SetMark(self, sel_start, sel_to)
490
491 self._ignoreChange = False # tell _OnTextChange() to pay attn again
492
493 ## dbg('computed selection:', sel_start, sel_to, indent=0, suspend=0)
494 return sel_start, sel_to
495 else:
496 def GetMark(self):
497 ## dbg('MaskedComboBox::GetMark()', indent = 1)
498 ret = wx.ComboBox.GetMark(self)
499 ## dbg('returned', ret, indent=0)
500 return ret
501
502
503 def SetSelection(self, index):
504 """
505 Necessary override for bookkeeping on choice selection, to keep current value
506 current.
507 """
508 ## dbg('MaskedComboBox::SetSelection(%d)' % index)
509 if self._mask:
510 self._prevValue = self._curValue
511 self._curValue = self._choices[index]
512 self._ctrl_constraints._autoCompleteIndex = index
513 wx.ComboBox.SetSelection(self, index)
514
515
516 def _OnKeyDownInComboBox(self, event):
517 """
518 This function is necessary because navigation and control key
519 events do not seem to normally be seen by the wxComboBox's
520 EVT_CHAR routine. (Tabs don't seem to be visible no matter
521 what... {:-( )
522 """
523 if event.GetKeyCode() in self._nav + self._control:
524 self._OnChar(event)
525 return
526 else:
527 event.Skip() # let mixin default KeyDown behavior occur
528
529
530 def _OnSelectChoice(self, event):
531 """
532 This function appears to be necessary, because the processing done
533 on the text of the control somehow interferes with the combobox's
534 selection mechanism for the arrow keys.
535 """
536 ## dbg('MaskedComboBox::OnSelectChoice', indent=1)
537
538 if not self._mask:
539 event.Skip()
540 return
541
542 value = self.GetValue().strip()
543
544 if self._ctrl_constraints._compareNoCase:
545 value = value.lower()
546
547 if event.GetKeyCode() == wx.WXK_UP:
548 direction = -1
549 else:
550 direction = 1
551 match_index, partial_match = self._autoComplete(
552 direction,
553 self._ctrl_constraints._compareChoices,
554 value,
555 self._ctrl_constraints._compareNoCase,
556 current_index = self._ctrl_constraints._autoCompleteIndex)
557 if match_index is not None:
558 ## dbg('setting selection to', match_index)
559 # issue appropriate event to outside:
560 self._OnAutoSelect(self._ctrl_constraints, match_index=match_index)
561 self._CheckValid()
562 keep_processing = False
563 else:
564 pos = self._adjustPos(self._GetInsertionPoint(), event.GetKeyCode())
565 field = self._FindField(pos)
566 if self.IsEmpty() or not field._hasList:
567 ## dbg('selecting 1st value in list')
568 self._OnAutoSelect(self._ctrl_constraints, match_index=0)
569 self._CheckValid()
570 keep_processing = False
571 else:
572 # attempt field-level auto-complete
573 ## dbg(indent=0)
574 keep_processing = self._OnAutoCompleteField(event)
575 ## dbg('keep processing?', keep_processing, indent=0)
576 return keep_processing
577
578
579 def _OnAutoSelect(self, field, match_index):
580 """
581 Override mixin (empty) autocomplete handler, so that autocompletion causes
582 combobox to update appropriately.
583 """
584 ## dbg('MaskedComboBox::OnAutoSelect', field._index, indent=1)
585 ## field._autoCompleteIndex = match_index
586 if field == self._ctrl_constraints:
587 self.SetSelection(match_index)
588 ## dbg('issuing combo selection event')
589 self.GetEventHandler().ProcessEvent(
590 MaskedComboBoxSelectEvent( self.GetId(), match_index, self ) )
591 self._CheckValid()
592 ## dbg('field._autoCompleteIndex:', match_index)
593 ## dbg('self.GetSelection():', self.GetSelection())
594 end = self._goEnd(getPosOnly=True)
595 ## dbg('scheduling set of end position to:', end)
596 # work around bug in wx 2.5
597 wx.CallAfter(self.SetInsertionPoint, 0)
598 wx.CallAfter(self.SetInsertionPoint, end)
599 ## dbg(indent=0)
600
601
602 def _OnReturn(self, event):
603 """
604 For wx.ComboBox, it seems that if you hit return when the dropdown is
605 dropped, the event that dismisses the dropdown will also blank the
606 control, because of the implementation of wxComboBox. So this function
607 examines the selection and if it is -1, and the value according to
608 (the base control!) is a value in the list, then it schedules a
609 programmatic wxComboBox.SetSelection() call to pick the appropriate
610 item in the list. (and then does the usual OnReturn bit.)
611 """
612 ## dbg('MaskedComboBox::OnReturn', indent=1)
613 ## dbg('current value: "%s"' % self.GetValue(), 'current index:', self.GetSelection())
614 if self.GetSelection() == -1 and self.GetValue().lower().strip() in self._ctrl_constraints._compareChoices:
615 wx.CallAfter(self.SetSelection, self._ctrl_constraints._autoCompleteIndex)
616
617 event.m_keyCode = wx.WXK_TAB
618 event.Skip()
619 ## dbg(indent=0)
620
621
622 class ComboBox( BaseMaskedComboBox, MaskedEditAccessorsMixin ):
623 """
624 The "user-visible" masked combobox control, this class is
625 identical to the BaseMaskedComboBox class it's derived from.
626 (This extra level of inheritance allows us to add the generic
627 set of masked edit parameters only to this class while allowing
628 other classes to derive from the "base" masked combobox control,
629 and provide a smaller set of valid accessor functions.)
630 See BaseMaskedComboBox for available methods.
631 """
632 pass
633
634
635 class PreMaskedComboBox( BaseMaskedComboBox, MaskedEditAccessorsMixin ):
636 """
637 This class exists to support the use of XRC subclassing.
638 """
639 # This should really be wx.EVT_WINDOW_CREATE but it is not
640 # currently delivered for native controls on all platforms, so
641 # we'll use EVT_SIZE instead. It should happen shortly after the
642 # control is created as the control is set to its "best" size.
643 _firstEventType = wx.EVT_SIZE
644
645 def __init__(self):
646 pre = wx.PreComboBox()
647 self.PostCreate(pre)
648 self.Bind(self._firstEventType, self.OnCreate)
649
650
651 def OnCreate(self, evt):
652 self.Unbind(self._firstEventType)
653 self._PostInit()
654
655 __i = 0
656 ## CHANGELOG:
657 ## ====================
658 ## Version 1.3
659 ## 1. Made definition of "hack" GetMark conditional on base class not
660 ## implementing it properly, to allow for migration in wx code base
661 ## while taking advantage of improvements therein for some platforms.
662 ##
663 ## Version 1.2
664 ## 1. Converted docstrings to reST format, added doc for ePyDoc.
665 ## 2. Renamed helper functions, vars etc. not intended to be visible in public
666 ## interface to code.
667 ##
668 ## Version 1.1
669 ## 1. Added .SetFont() method that properly resizes control
670 ## 2. Modified control to support construction via XRC mechanism.
671 ## 3. Added AppendItems() to conform with latest combobox.