]> git.saurik.com Git - wxWidgets.git/blob - wxPython/wx/lib/masked/combobox.py
wx.lib.masked: Patch from Will Sadkin. Includes Unicode fixes, plus
[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.SetInitialSize((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.SetInitialSize((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 IsEmpty(*args, **kw):
228 return MaskedEditMixin.IsEmpty(*args, **kw)
229
230
231 def _GetValue(self):
232 """
233 Allow mixin to get the raw value of the control with this function.
234 REQUIRED by any class derived from MaskedEditMixin.
235 """
236 return self.GetValue()
237
238 def _SetValue(self, value):
239 """
240 Allow mixin to set the raw value of the control with this function.
241 REQUIRED by any class derived from MaskedEditMixin.
242 """
243 # For wxComboBox, ensure that values are properly padded so that
244 # if varying length choices are supplied, they always show up
245 # in the window properly, and will be the appropriate length
246 # to match the mask:
247 if self._ctrl_constraints._alignRight:
248 value = value.rjust(self._masklength)
249 else:
250 value = value.ljust(self._masklength)
251
252 # Record current selection and insertion point, for undo
253 self._prevSelection = self._GetSelection()
254 self._prevInsertionPoint = self._GetInsertionPoint()
255 wx.ComboBox.SetValue(self, value)
256 # text change events don't always fire, so we check validity here
257 # to make certain formatting is applied:
258 self._CheckValid()
259
260 def SetValue(self, value):
261 """
262 This function redefines the externally accessible .SetValue to be
263 a smart "paste" of the text in question, so as not to corrupt the
264 masked control. NOTE: this must be done in the class derived
265 from the base wx control.
266 """
267 if not self._mask:
268 wx.ComboBox.SetValue(value) # revert to base control behavior
269 return
270 # else...
271 # empty previous contents, replacing entire value:
272 self._SetInsertionPoint(0)
273 self._SetSelection(0, self._masklength)
274
275 if( len(value) < self._masklength # value shorter than control
276 and (self._isFloat or self._isInt) # and it's a numeric control
277 and self._ctrl_constraints._alignRight ): # and it's a right-aligned control
278 # try to intelligently "pad out" the value to the right size:
279 value = self._template[0:self._masklength - len(value)] + value
280 ## dbg('padded value = "%s"' % value)
281
282 # For wxComboBox, ensure that values are properly padded so that
283 # if varying length choices are supplied, they always show up
284 # in the window properly, and will be the appropriate length
285 # to match the mask:
286 elif self._ctrl_constraints._alignRight:
287 value = value.rjust(self._masklength)
288 else:
289 value = value.ljust(self._masklength)
290
291
292 # make SetValue behave the same as if you had typed the value in:
293 try:
294 value, replace_to = self._Paste(value, raise_on_invalid=True, just_return_value=True)
295 if self._isFloat:
296 self._isNeg = False # (clear current assumptions)
297 value = self._adjustFloat(value)
298 elif self._isInt:
299 self._isNeg = False # (clear current assumptions)
300 value = self._adjustInt(value)
301 elif self._isDate and not self.IsValid(value) and self._4digityear:
302 value = self._adjustDate(value, fixcentury=True)
303 except ValueError:
304 # If date, year might be 2 digits vs. 4; try adjusting it:
305 if self._isDate and self._4digityear:
306 dateparts = value.split(' ')
307 dateparts[0] = self._adjustDate(dateparts[0], fixcentury=True)
308 value = string.join(dateparts, ' ')
309 ## dbg('adjusted value: "%s"' % value)
310 value = self._Paste(value, raise_on_invalid=True, just_return_value=True)
311 else:
312 raise
313
314 self._SetValue(value)
315 #### dbg('queuing insertion after .SetValue', replace_to)
316 wx.CallAfter(self._SetInsertionPoint, replace_to)
317 wx.CallAfter(self._SetSelection, replace_to, replace_to)
318
319
320 def _Refresh(self):
321 """
322 Allow mixin to refresh the base control with this function.
323 REQUIRED by any class derived from MaskedEditMixin.
324 """
325 wx.ComboBox.Refresh(self)
326
327 def Refresh(self):
328 """
329 This function redefines the externally accessible .Refresh() to
330 validate the contents of the masked control as it refreshes.
331 NOTE: this must be done in the class derived from the base wx control.
332 """
333 self._CheckValid()
334 self._Refresh()
335
336
337 def _IsEditable(self):
338 """
339 Allow mixin to determine if the base control is editable with this function.
340 REQUIRED by any class derived from MaskedEditMixin.
341 """
342 return not self.__readonly
343
344
345 def Cut(self):
346 """
347 This function redefines the externally accessible .Cut to be
348 a smart "erase" of the text in question, so as not to corrupt the
349 masked control. NOTE: this must be done in the class derived
350 from the base wx control.
351 """
352 if self._mask:
353 self._Cut() # call the mixin's Cut method
354 else:
355 wx.ComboBox.Cut(self) # else revert to base control behavior
356
357
358 def Paste(self):
359 """
360 This function redefines the externally accessible .Paste to be
361 a smart "paste" of the text in question, so as not to corrupt the
362 masked control. NOTE: this must be done in the class derived
363 from the base wx control.
364 """
365 if self._mask:
366 self._Paste() # call the mixin's Paste method
367 else:
368 wx.ComboBox.Paste(self) # else revert to base control behavior
369
370
371 def Undo(self):
372 """
373 This function defines the undo operation for the control. (The default
374 undo is 1-deep.)
375 """
376 if self._mask:
377 self._Undo()
378 else:
379 wx.ComboBox.Undo() # else revert to base control behavior
380
381 def Append( self, choice, clientData=None ):
382 """
383 This base control function override is necessary so the control can keep track
384 of any additions to the list of choices, because wx.ComboBox doesn't have an
385 accessor for the choice list. The code here is the same as in the
386 SetParameters() mixin function, but is done for the individual value
387 as appended, so the list can be built incrementally without speed penalty.
388 """
389 if self._mask:
390 if type(choice) not in (types.StringType, types.UnicodeType):
391 raise TypeError('%s: choices must be a sequence of strings' % str(self._index))
392 elif not self.IsValid(choice):
393 raise ValueError('%s: "%s" is not a valid value for the control as specified.' % (str(self._index), choice))
394
395 if not self._ctrl_constraints._choices:
396 self._ctrl_constraints._compareChoices = []
397 self._ctrl_constraints._choices = []
398 self._hasList = True
399
400 compareChoice = choice.strip()
401
402 if self._ctrl_constraints._compareNoCase:
403 compareChoice = compareChoice.lower()
404
405 if self._ctrl_constraints._alignRight:
406 choice = choice.rjust(self._masklength)
407 else:
408 choice = choice.ljust(self._masklength)
409 if self._ctrl_constraints._fillChar != ' ':
410 choice = choice.replace(' ', self._fillChar)
411 ## dbg('updated choice:', choice)
412
413
414 self._ctrl_constraints._compareChoices.append(compareChoice)
415 self._ctrl_constraints._choices.append(choice)
416 self._choices = self._ctrl_constraints._choices # (for shorthand)
417
418 if( not self.IsValid(choice) and
419 (not self._ctrl_constraints.IsEmpty(choice) or
420 (self._ctrl_constraints.IsEmpty(choice) and self._ctrl_constraints._validRequired) ) ):
421 raise ValueError('"%s" is not a valid value for the control "%s" as specified.' % (choice, self.name))
422
423 wx.ComboBox.Append(self, choice, clientData)
424
425
426 def AppendItems( self, choices ):
427 """
428 AppendItems() is handled in terms of Append, to avoid code replication.
429 """
430 for choice in choices:
431 self.Append(choice)
432
433
434 def Clear( self ):
435 """
436 This base control function override is necessary so the derived control can
437 keep track of any additions to the list of choices, because wx.ComboBox
438 doesn't have an accessor for the choice list.
439 """
440 if self._mask:
441 self._choices = []
442 self._ctrl_constraints._autoCompleteIndex = -1
443 if self._ctrl_constraints._choices:
444 self.SetCtrlParameters(choices=[])
445 wx.ComboBox.Clear(self)
446
447
448 def _OnCtrlParametersChanged(self):
449 """
450 This overrides the mixin's default OnCtrlParametersChanged to detect
451 changes in choice list, so masked.Combobox can update the base control:
452 """
453 if self.controlInitialized and self._choices != self._ctrl_constraints._choices:
454 wx.ComboBox.Clear(self)
455 self._choices = self._ctrl_constraints._choices
456 for choice in self._choices:
457 wx.ComboBox.Append( self, choice )
458
459
460 # Not all wx platform implementations have .GetMark, so we make the following test,
461 # and fall back to our old hack if they don't...
462 #
463 if not hasattr(wx.ComboBox, 'GetMark'):
464 def GetMark(self):
465 """
466 This function is a hack to make up for the fact that wx.ComboBox has no
467 method for returning the selected portion of its edit control. It
468 works, but has the nasty side effect of generating lots of intermediate
469 events.
470 """
471 ## dbg(suspend=1) # turn off debugging around this function
472 ## dbg('MaskedComboBox::GetMark', indent=1)
473 if self.__readonly:
474 ## dbg(indent=0)
475 return 0, 0 # no selection possible for editing
476 ## sel_start, sel_to = wxComboBox.GetMark(self) # what I'd *like* to have!
477 sel_start = sel_to = self.GetInsertionPoint()
478 ## dbg("current sel_start:", sel_start)
479 value = self.GetValue()
480 ## dbg('value: "%s"' % value)
481
482 self._ignoreChange = True # tell _OnTextChange() to ignore next event (if any)
483
484 wx.ComboBox.Cut(self)
485 newvalue = self.GetValue()
486 ## dbg("value after Cut operation:", newvalue)
487
488 if newvalue != value: # something was selected; calculate extent
489 ## dbg("something selected")
490 sel_to = sel_start + len(value) - len(newvalue)
491 wx.ComboBox.SetValue(self, value) # restore original value and selection (still ignoring change)
492 wx.ComboBox.SetInsertionPoint(self, sel_start)
493 wx.ComboBox.SetMark(self, sel_start, sel_to)
494
495 self._ignoreChange = False # tell _OnTextChange() to pay attn again
496
497 ## dbg('computed selection:', sel_start, sel_to, indent=0, suspend=0)
498 return sel_start, sel_to
499 else:
500 def GetMark(self):
501 ## dbg('MaskedComboBox::GetMark()', indent = 1)
502 ret = wx.ComboBox.GetMark(self)
503 ## dbg('returned', ret, indent=0)
504 return ret
505
506
507 def SetSelection(self, index):
508 """
509 Necessary override for bookkeeping on choice selection, to keep current value
510 current.
511 """
512 ## dbg('MaskedComboBox::SetSelection(%d)' % index)
513 if self._mask:
514 self._prevValue = self._curValue
515 self._curValue = self._choices[index]
516 self._ctrl_constraints._autoCompleteIndex = index
517 wx.ComboBox.SetSelection(self, index)
518
519
520 def _OnKeyDownInComboBox(self, event):
521 """
522 This function is necessary because navigation and control key
523 events do not seem to normally be seen by the wxComboBox's
524 EVT_CHAR routine. (Tabs don't seem to be visible no matter
525 what... {:-( )
526 """
527 if event.GetKeyCode() in self._nav + self._control:
528 self._OnChar(event)
529 return
530 else:
531 event.Skip() # let mixin default KeyDown behavior occur
532
533
534 def _OnSelectChoice(self, event):
535 """
536 This function appears to be necessary, because the processing done
537 on the text of the control somehow interferes with the combobox's
538 selection mechanism for the arrow keys.
539 """
540 ## dbg('MaskedComboBox::OnSelectChoice', indent=1)
541
542 if not self._mask:
543 event.Skip()
544 return
545
546 value = self.GetValue().strip()
547
548 if self._ctrl_constraints._compareNoCase:
549 value = value.lower()
550
551 if event.GetKeyCode() == wx.WXK_UP:
552 direction = -1
553 else:
554 direction = 1
555 match_index, partial_match = self._autoComplete(
556 direction,
557 self._ctrl_constraints._compareChoices,
558 value,
559 self._ctrl_constraints._compareNoCase,
560 current_index = self._ctrl_constraints._autoCompleteIndex)
561 if match_index is not None:
562 ## dbg('setting selection to', match_index)
563 # issue appropriate event to outside:
564 self._OnAutoSelect(self._ctrl_constraints, match_index=match_index)
565 self._CheckValid()
566 keep_processing = False
567 else:
568 pos = self._adjustPos(self._GetInsertionPoint(), event.GetKeyCode())
569 field = self._FindField(pos)
570 if self.IsEmpty() or not field._hasList:
571 ## dbg('selecting 1st value in list')
572 self._OnAutoSelect(self._ctrl_constraints, match_index=0)
573 self._CheckValid()
574 keep_processing = False
575 else:
576 # attempt field-level auto-complete
577 ## dbg(indent=0)
578 keep_processing = self._OnAutoCompleteField(event)
579 ## dbg('keep processing?', keep_processing, indent=0)
580 return keep_processing
581
582
583 def _OnAutoSelect(self, field, match_index):
584 """
585 Override mixin (empty) autocomplete handler, so that autocompletion causes
586 combobox to update appropriately.
587 """
588 ## dbg('MaskedComboBox::OnAutoSelect', field._index, indent=1)
589 ## field._autoCompleteIndex = match_index
590 if field == self._ctrl_constraints:
591 self.SetSelection(match_index)
592 ## dbg('issuing combo selection event')
593 self.GetEventHandler().ProcessEvent(
594 MaskedComboBoxSelectEvent( self.GetId(), match_index, self ) )
595 self._CheckValid()
596 ## dbg('field._autoCompleteIndex:', match_index)
597 ## dbg('self.GetSelection():', self.GetSelection())
598 end = self._goEnd(getPosOnly=True)
599 ## dbg('scheduling set of end position to:', end)
600 # work around bug in wx 2.5
601 wx.CallAfter(self.SetInsertionPoint, 0)
602 wx.CallAfter(self.SetInsertionPoint, end)
603 ## dbg(indent=0)
604
605
606 def _OnReturn(self, event):
607 """
608 For wx.ComboBox, it seems that if you hit return when the dropdown is
609 dropped, the event that dismisses the dropdown will also blank the
610 control, because of the implementation of wxComboBox. So this function
611 examines the selection and if it is -1, and the value according to
612 (the base control!) is a value in the list, then it schedules a
613 programmatic wxComboBox.SetSelection() call to pick the appropriate
614 item in the list. (and then does the usual OnReturn bit.)
615 """
616 ## dbg('MaskedComboBox::OnReturn', indent=1)
617 ## dbg('current value: "%s"' % self.GetValue(), 'current index:', self.GetSelection())
618 if self.GetSelection() == -1 and self.GetValue().lower().strip() in self._ctrl_constraints._compareChoices:
619 wx.CallAfter(self.SetSelection, self._ctrl_constraints._autoCompleteIndex)
620
621 event.m_keyCode = wx.WXK_TAB
622 event.Skip()
623 ## dbg(indent=0)
624
625
626 class ComboBox( BaseMaskedComboBox, MaskedEditAccessorsMixin ):
627 """
628 The "user-visible" masked combobox control, this class is
629 identical to the BaseMaskedComboBox class it's derived from.
630 (This extra level of inheritance allows us to add the generic
631 set of masked edit parameters only to this class while allowing
632 other classes to derive from the "base" masked combobox control,
633 and provide a smaller set of valid accessor functions.)
634 See BaseMaskedComboBox for available methods.
635 """
636 pass
637
638
639 class PreMaskedComboBox( BaseMaskedComboBox, MaskedEditAccessorsMixin ):
640 """
641 This class exists to support the use of XRC subclassing.
642 """
643 # This should really be wx.EVT_WINDOW_CREATE but it is not
644 # currently delivered for native controls on all platforms, so
645 # we'll use EVT_SIZE instead. It should happen shortly after the
646 # control is created as the control is set to its "best" size.
647 _firstEventType = wx.EVT_SIZE
648
649 def __init__(self):
650 pre = wx.PreComboBox()
651 self.PostCreate(pre)
652 self.Bind(self._firstEventType, self.OnCreate)
653
654
655 def OnCreate(self, evt):
656 self.Unbind(self._firstEventType)
657 self._PostInit()
658
659 __i = 0
660 ## CHANGELOG:
661 ## ====================
662 ## Version 1.3
663 ## 1. Made definition of "hack" GetMark conditional on base class not
664 ## implementing it properly, to allow for migration in wx code base
665 ## while taking advantage of improvements therein for some platforms.
666 ##
667 ## Version 1.2
668 ## 1. Converted docstrings to reST format, added doc for ePyDoc.
669 ## 2. Renamed helper functions, vars etc. not intended to be visible in public
670 ## interface to code.
671 ##
672 ## Version 1.1
673 ## 1. Added .SetFont() method that properly resizes control
674 ## 2. Modified control to support construction via XRC mechanism.
675 ## 3. Added AppendItems() to conform with latest combobox.