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
50 class MaskedComboBoxEventHandler(wx
.EvtHandler
):
52 This handler ensures that the derived control can react to events
53 from the base control before any external handlers run, to ensure
56 def __init__(self
, combobox
):
57 wx
.EvtHandler
.__init
__(self
)
58 self
.combobox
= combobox
59 combobox
.PushEventHandler(self
)
60 self
.Bind(wx
.EVT_SET_FOCUS
, self
.combobox
._OnFocus
) ## defeat automatic full selection
61 self
.Bind(wx
.EVT_KILL_FOCUS
, self
.combobox
._OnKillFocus
) ## run internal validator
62 self
.Bind(wx
.EVT_LEFT_DCLICK
, self
.combobox
._OnDoubleClick
) ## select field under cursor on dclick
63 self
.Bind(wx
.EVT_RIGHT_UP
, self
.combobox
._OnContextMenu
) ## bring up an appropriate context menu
64 self
.Bind(wx
.EVT_CHAR
, self
.combobox
._OnChar
) ## handle each keypress
65 self
.Bind(wx
.EVT_KEY_DOWN
, self
.combobox
._OnKeyDownInComboBox
) ## for special processing of up/down keys
66 self
.Bind(wx
.EVT_KEY_DOWN
, self
.combobox
._OnKeyDown
) ## for processing the rest of the control keys
67 ## (next in evt chain)
68 self
.Bind(wx
.EVT_COMBOBOX
, self
.combobox
._OnDropdownSelect
) ## to bring otherwise completely independent base
69 ## ctrl selection into maskededit framework
70 self
.Bind(wx
.EVT_TEXT
, self
.combobox
._OnTextChange
) ## color control appropriately & keep
71 ## track of previous value for undo
75 class BaseMaskedComboBox( wx
.ComboBox
, MaskedEditMixin
):
77 Base class for generic masked edit comboboxes; allows auto-complete of values.
78 It is not meant to be instantiated directly, but rather serves as a base class
79 for any subsequent refinements.
81 def __init__( self
, parent
, id=-1, value
= '',
82 pos
= wx
.DefaultPosition
,
83 size
= wx
.DefaultSize
,
85 style
= wx
.CB_DROPDOWN
,
86 validator
= wx
.DefaultValidator
,
87 name
= "maskedComboBox",
88 setupEventHandling
= True, ## setup event handling by default):
92 kwargs
['choices'] = choices
## set up maskededit to work with choice list too
94 ## Since combobox completion is case-insensitive, always validate same way
95 if not kwargs
.has_key('compareNoCase'):
96 kwargs
['compareNoCase'] = True
98 MaskedEditMixin
.__init
__( self
, name
, **kwargs
)
100 self
._choices
= self
._ctrl
_constraints
._choices
101 ## dbg('self._choices:', self._choices)
103 if self
._ctrl
_constraints
._alignRight
:
104 choices
= [choice
.rjust(self
._masklength
) for choice
in choices
]
106 choices
= [choice
.ljust(self
._masklength
) for choice
in choices
]
108 wx
.ComboBox
.__init
__(self
, parent
, id, value
='',
109 pos
=pos
, size
= size
,
110 choices
=choices
, style
=style|wx
.WANTS_CHARS
,
113 self
.controlInitialized
= True
115 self
._PostInit
(style
=style
, setupEventHandling
=setupEventHandling
,
116 name
=name
, value
=value
, **kwargs
)
119 def _PostInit(self
, style
=wx
.CB_DROPDOWN
,
120 setupEventHandling
= True, ## setup event handling by default):
121 name
= "maskedComboBox", value
='', **kwargs
):
123 # This is necessary, because wxComboBox currently provides no
124 # method for determining later if this was specified in the
125 # constructor for the control...
126 self
.__readonly
= style
& wx
.CB_READONLY
== wx
.CB_READONLY
128 if not hasattr(self
, 'controlInitialized'):
130 self
.controlInitialized
= True ## must have been called via XRC, therefore base class is constructed
131 if not kwargs
.has_key('choices'):
133 kwargs
['choices'] = choices
## set up maskededit to work with choice list too
136 ## Since combobox completion is case-insensitive, always validate same way
137 if not kwargs
.has_key('compareNoCase'):
138 kwargs
['compareNoCase'] = True
140 MaskedEditMixin
.__init
__( self
, name
, **kwargs
)
142 self
._choices
= self
._ctrl
_constraints
._choices
143 ## dbg('self._choices:', self._choices)
145 if self
._ctrl
_constraints
._alignRight
:
146 choices
= [choice
.rjust(self
._masklength
) for choice
in choices
]
148 choices
= [choice
.ljust(self
._masklength
) for choice
in choices
]
149 wx
.ComboBox
.Clear(self
)
150 wx
.ComboBox
.AppendItems(self
, choices
)
153 # Set control font - fixed width by default
157 self
.SetClientSize(self
._CalcSize
())
158 width
= self
.GetSize().width
159 height
= self
.GetBestSize().height
160 self
.SetInitialSize((width
, height
))
164 # ensure value is width of the mask of the control:
165 if self
._ctrl
_constraints
._alignRight
:
166 value
= value
.rjust(self
._masklength
)
168 value
= value
.ljust(self
._masklength
)
171 self
.SetStringSelection(value
)
173 self
._SetInitialValue
(value
)
176 self
._SetKeycodeHandler
(wx
.WXK_UP
, self
._OnSelectChoice
)
177 self
._SetKeycodeHandler
(wx
.WXK_DOWN
, self
._OnSelectChoice
)
179 self
.replace_next_combobox_event
= False
180 self
.correct_selection
= -1
182 if setupEventHandling
:
183 ## Setup event handling functions through event handler object,
184 ## to guarantee processing prior to giving event callbacks from
185 ## outside the class:
186 self
.evt_handler
= MaskedComboBoxEventHandler(self
)
187 self
.Bind(wx
.EVT_WINDOW_DESTROY
, self
.OnWindowDestroy
)
192 return "<MaskedComboBox: %s>" % self
.GetValue()
195 def OnWindowDestroy(self
, event
):
196 # clean up associated event handler object:
197 if self
.RemoveEventHandler(self
.evt_handler
):
198 self
.evt_handler
.Destroy()
202 def _CalcSize(self
, size
=None):
204 Calculate automatic size if allowed; augment base mixin function
205 to account for the selector button.
207 size
= self
._calcSize
(size
)
208 return (size
[0]+20, size
[1])
211 def SetFont(self
, *args
, **kwargs
):
212 """ Set the font, then recalculate control size, if appropriate. """
213 wx
.ComboBox
.SetFont(self
, *args
, **kwargs
)
215 ## dbg('calculated size:', self._CalcSize())
216 self
.SetClientSize(self
._CalcSize
())
217 width
= self
.GetSize().width
218 height
= self
.GetBestSize().height
219 ## dbg('setting client size to:', (width, height))
220 self
.SetInitialSize((width
, height
))
223 def _GetSelection(self
):
225 Allow mixin to get the text selection of this control.
226 REQUIRED by any class derived from MaskedEditMixin.
228 ## dbg('MaskedComboBox::_GetSelection()')
229 return self
.GetMark()
231 def _SetSelection(self
, sel_start
, sel_to
):
233 Allow mixin to set the text selection of this control.
234 REQUIRED by any class derived from MaskedEditMixin.
236 ## dbg('MaskedComboBox::_SetSelection: setting mark to (%d, %d)' % (sel_start, sel_to))
237 return self
.SetMark( sel_start
, sel_to
)
240 def _GetInsertionPoint(self
):
241 ## dbg('MaskedComboBox::_GetInsertionPoint()', indent=1)
242 ## ret = self.GetInsertionPoint()
243 # work around new bug in 2.5, in which the insertion point
244 # returned is always at the right side of the selection,
245 # rather than the start, as is the case with TextCtrl.
246 ret
= self
.GetMark()[0]
247 ## dbg('returned', ret, indent=0)
250 def _SetInsertionPoint(self
, pos
):
251 ## dbg('MaskedComboBox::_SetInsertionPoint(%d)' % pos)
252 self
.SetInsertionPoint(pos
)
255 def IsEmpty(*args
, **kw
):
256 return MaskedEditMixin
.IsEmpty(*args
, **kw
)
261 Allow mixin to get the raw value of the control with this function.
262 REQUIRED by any class derived from MaskedEditMixin.
264 return self
.GetValue()
266 def _SetValue(self
, value
):
268 Allow mixin to set the raw value of the control with this function.
269 REQUIRED by any class derived from MaskedEditMixin.
271 # For wxComboBox, ensure that values are properly padded so that
272 # if varying length choices are supplied, they always show up
273 # in the window properly, and will be the appropriate length
275 if self
._ctrl
_constraints
._alignRight
:
276 value
= value
.rjust(self
._masklength
)
278 value
= value
.ljust(self
._masklength
)
280 # Record current selection and insertion point, for undo
281 self
._prevSelection
= self
._GetSelection
()
282 self
._prevInsertionPoint
= self
._GetInsertionPoint
()
283 ## dbg('MaskedComboBox::_SetValue(%s), selection beforehand: %d' % (value, self.GetSelection()))
284 wx
.ComboBox
.SetValue(self
, value
)
285 ## dbg('MaskedComboBox::_SetValue(%s), selection now: %d' % (value, self.GetSelection()))
286 # text change events don't always fire, so we check validity here
287 # to make certain formatting is applied:
290 def SetValue(self
, value
):
292 This function redefines the externally accessible .SetValue to be
293 a smart "paste" of the text in question, so as not to corrupt the
294 masked control. NOTE: this must be done in the class derived
295 from the base wx control.
297 ## dbg('MaskedComboBox::SetValue(%s)' % value, indent=1)
299 wx
.ComboBox
.SetValue(value
) # revert to base control behavior
300 ## dbg('no mask; deferring to base class', indent=0)
303 # empty previous contents, replacing entire value:
304 ## dbg('MaskedComboBox::SetValue: selection beforehand: %d' % (self.GetSelection()))
305 self
._SetInsertionPoint
(0)
306 self
._SetSelection
(0, self
._masklength
)
308 if( len(value
) < self
._masklength
# value shorter than control
309 and (self
._isFloat
or self
._isInt
) # and it's a numeric control
310 and self
._ctrl
_constraints
._alignRight
): # and it's a right-aligned control
311 # try to intelligently "pad out" the value to the right size:
312 value
= self
._template
[0:self
._masklength
- len(value
)] + value
313 ## dbg('padded value = "%s"' % value)
315 # For wxComboBox, ensure that values are properly padded so that
316 # if varying length choices are supplied, they always show up
317 # in the window properly, and will be the appropriate length
319 elif self
._ctrl
_constraints
._alignRight
:
320 value
= value
.rjust(self
._masklength
)
322 value
= value
.ljust(self
._masklength
)
325 # make SetValue behave the same as if you had typed the value in:
327 value
, replace_to
= self
._Paste
(value
, raise_on_invalid
=True, just_return_value
=True)
329 self
._isNeg
= False # (clear current assumptions)
330 value
= self
._adjustFloat
(value
)
332 self
._isNeg
= False # (clear current assumptions)
333 value
= self
._adjustInt
(value
)
334 elif self
._isDate
and not self
.IsValid(value
) and self
._4digityear
:
335 value
= self
._adjustDate
(value
, fixcentury
=True)
337 # If date, year might be 2 digits vs. 4; try adjusting it:
338 if self
._isDate
and self
._4digityear
:
339 dateparts
= value
.split(' ')
340 dateparts
[0] = self
._adjustDate
(dateparts
[0], fixcentury
=True)
341 value
= string
.join(dateparts
, ' ')
342 value
= self
._Paste
(value
, raise_on_invalid
=True, just_return_value
=True)
345 ## dbg('adjusted value: "%s"' % value)
347 # Attempt to compensate for fact that calling .SetInsertionPoint() makes the
348 # selection index -1, even if the resulting set value is in the list.
349 # So, if we are setting a value that's in the list, use index selection instead.
350 if value
in self
._choices
:
351 index
= self
._choices
.index(value
)
352 self
._prevValue
= self
._curValue
353 self
._curValue
= self
._choices
[index
]
354 self
._ctrl
_constraints
._autoCompleteIndex
= index
355 self
.SetSelection(index
)
357 self
._SetValue
(value
)
358 #### dbg('queuing insertion after .SetValue', replace_to)
359 wx
.CallAfter(self
._SetInsertionPoint
, replace_to
)
360 wx
.CallAfter(self
._SetSelection
, replace_to
, replace_to
)
366 Allow mixin to refresh the base control with this function.
367 REQUIRED by any class derived from MaskedEditMixin.
369 wx
.ComboBox
.Refresh(self
)
373 This function redefines the externally accessible .Refresh() to
374 validate the contents of the masked control as it refreshes.
375 NOTE: this must be done in the class derived from the base wx control.
381 def _IsEditable(self
):
383 Allow mixin to determine if the base control is editable with this function.
384 REQUIRED by any class derived from MaskedEditMixin.
386 return not self
.__readonly
391 This function redefines the externally accessible .Cut to be
392 a smart "erase" of the text in question, so as not to corrupt the
393 masked control. NOTE: this must be done in the class derived
394 from the base wx control.
397 self
._Cut
() # call the mixin's Cut method
399 wx
.ComboBox
.Cut(self
) # else revert to base control behavior
404 This function redefines the externally accessible .Paste to be
405 a smart "paste" of the text in question, so as not to corrupt the
406 masked control. NOTE: this must be done in the class derived
407 from the base wx control.
410 self
._Paste
() # call the mixin's Paste method
412 wx
.ComboBox
.Paste(self
) # else revert to base control behavior
417 This function defines the undo operation for the control. (The default
423 wx
.ComboBox
.Undo() # else revert to base control behavior
425 def Append( self
, choice
, clientData
=None ):
427 This base control function override is necessary so the control can keep track
428 of any additions to the list of choices, because wx.ComboBox doesn't have an
429 accessor for the choice list. The code here is the same as in the
430 SetParameters() mixin function, but is done for the individual value
431 as appended, so the list can be built incrementally without speed penalty.
434 if type(choice
) not in (types
.StringType
, types
.UnicodeType
):
435 raise TypeError('%s: choices must be a sequence of strings' % str(self
._index
))
436 elif not self
.IsValid(choice
):
437 raise ValueError('%s: "%s" is not a valid value for the control as specified.' % (str(self
._index
), choice
))
439 if not self
._ctrl
_constraints
._choices
:
440 self
._ctrl
_constraints
._compareChoices
= []
441 self
._ctrl
_constraints
._choices
= []
444 compareChoice
= choice
.strip()
446 if self
._ctrl
_constraints
._compareNoCase
:
447 compareChoice
= compareChoice
.lower()
449 if self
._ctrl
_constraints
._alignRight
:
450 choice
= choice
.rjust(self
._masklength
)
452 choice
= choice
.ljust(self
._masklength
)
453 if self
._ctrl
_constraints
._fillChar
!= ' ':
454 choice
= choice
.replace(' ', self
._fillChar
)
455 ## dbg('updated choice:', choice)
458 self
._ctrl
_constraints
._compareChoices
.append(compareChoice
)
459 self
._ctrl
_constraints
._choices
.append(choice
)
460 self
._choices
= self
._ctrl
_constraints
._choices
# (for shorthand)
462 if( not self
.IsValid(choice
) and
463 (not self
._ctrl
_constraints
.IsEmpty(choice
) or
464 (self
._ctrl
_constraints
.IsEmpty(choice
) and self
._ctrl
_constraints
._validRequired
) ) ):
465 raise ValueError('"%s" is not a valid value for the control "%s" as specified.' % (choice
, self
.name
))
467 wx
.ComboBox
.Append(self
, choice
, clientData
)
470 def AppendItems( self
, choices
):
472 AppendItems() is handled in terms of Append, to avoid code replication.
474 for choice
in choices
:
480 This base control function override is necessary so the derived control can
481 keep track of any additions to the list of choices, because wx.ComboBox
482 doesn't have an accessor for the choice list.
486 self
._ctrl
_constraints
._autoCompleteIndex
= -1
487 if self
._ctrl
_constraints
._choices
:
488 self
.SetCtrlParameters(choices
=[])
489 wx
.ComboBox
.Clear(self
)
492 def _OnCtrlParametersChanged(self
):
494 This overrides the mixin's default OnCtrlParametersChanged to detect
495 changes in choice list, so masked.Combobox can update the base control:
497 if self
.controlInitialized
and self
._choices
!= self
._ctrl
_constraints
._choices
:
498 wx
.ComboBox
.Clear(self
)
499 self
._choices
= self
._ctrl
_constraints
._choices
500 for choice
in self
._choices
:
501 wx
.ComboBox
.Append( self
, choice
)
504 # Not all wx platform implementations have .GetMark, so we make the following test,
505 # and fall back to our old hack if they don't...
507 if not hasattr(wx
.ComboBox
, 'GetMark'):
510 This function is a hack to make up for the fact that wx.ComboBox has no
511 method for returning the selected portion of its edit control. It
512 works, but has the nasty side effect of generating lots of intermediate
515 ## dbg(suspend=1) # turn off debugging around this function
516 ## dbg('MaskedComboBox::GetMark', indent=1)
519 return 0, 0 # no selection possible for editing
520 ## sel_start, sel_to = wxComboBox.GetMark(self) # what I'd *like* to have!
521 sel_start
= sel_to
= self
.GetInsertionPoint()
522 ## dbg("current sel_start:", sel_start)
523 value
= self
.GetValue()
524 ## dbg('value: "%s"' % value)
526 self
._ignoreChange
= True # tell _OnTextChange() to ignore next event (if any)
528 wx
.ComboBox
.Cut(self
)
529 newvalue
= self
.GetValue()
530 ## dbg("value after Cut operation:", newvalue)
532 if newvalue
!= value
: # something was selected; calculate extent
533 ## dbg("something selected")
534 sel_to
= sel_start
+ len(value
) - len(newvalue
)
535 wx
.ComboBox
.SetValue(self
, value
) # restore original value and selection (still ignoring change)
536 wx
.ComboBox
.SetInsertionPoint(self
, sel_start
)
537 wx
.ComboBox
.SetMark(self
, sel_start
, sel_to
)
539 self
._ignoreChange
= False # tell _OnTextChange() to pay attn again
541 ## dbg('computed selection:', sel_start, sel_to, indent=0, suspend=0)
542 return sel_start
, sel_to
545 ## dbg('MaskedComboBox::GetMark()', indent = 1)
546 ret
= wx
.ComboBox
.GetMark(self
)
547 ## dbg('returned', ret, indent=0)
551 def SetSelection(self
, index
):
553 Necessary override for bookkeeping on choice selection, to keep current value
556 ## dbg('MaskedComboBox::SetSelection(%d)' % index, indent=1)
558 self
._prevValue
= self
._curValue
559 self
._ctrl
_constraints
._autoCompleteIndex
= index
561 self
._curValue
= self
._choices
[index
]
563 self
._curValue
= None
564 wx
.ComboBox
.SetSelection(self
, index
)
565 ## dbg('selection now: %d' % self.GetCurrentSelection(), indent=0)
568 def _OnKeyDownInComboBox(self
, event
):
570 This function is necessary because navigation and control key events
571 do not seem to normally be seen by the wxComboBox's EVT_CHAR routine.
572 (Tabs don't seem to be visible no matter what, except for CB_READONLY
573 controls, for some bizarre reason... {:-( )
575 key
= event
.GetKeyCode()
576 ## dbg('MaskedComboBox::OnKeyDownInComboBox(%d)' % key)
577 if event
.GetKeyCode() in self
._nav
+ self
._control
:
578 if not self
._IsEditable
():
579 # WANTS_CHARS with CB_READONLY apparently prevents navigation on WXK_TAB;
580 # ensure we can still navigate properly, as maskededit mixin::OnChar assumes
581 # that event.Skip() will just work, but it doesn't:
582 if self
._keyhandlers
.has_key(key
):
583 self
._keyhandlers
[key
](event
)
586 ## dbg('calling OnChar()')
589 event
.Skip() # let mixin default KeyDown behavior occur
593 def _OnDropdownSelect(self
, event
):
595 This function appears to be necessary because dropdown selection seems to
596 manipulate the contents of the control in an inconsistent way, properly
597 changing the selection index, but *not* the value. (!) Calling SetSelection()
598 on a selection event for the same selection would seem like a nop, but it seems to
601 ## dbg('MaskedComboBox::OnDropdownSelect(%d)' % event.GetSelection(), indent=1)
602 if self
.replace_next_combobox_event
:
603 ## dbg('replacing EVT_COMBOBOX')
604 self
.replace_next_combobox_event
= False
605 self
._OnAutoSelect
(self
._ctrl
_constraints
, self
.correct_selection
)
607 ## dbg('skipping EVT_COMBOBOX')
612 def _OnSelectChoice(self
, event
):
614 This function appears to be necessary, because the processing done
615 on the text of the control somehow interferes with the combobox's
616 selection mechanism for the arrow keys.
618 ## dbg('MaskedComboBox::OnSelectChoice', indent=1)
624 value
= self
.GetValue().strip()
626 if self
._ctrl
_constraints
._compareNoCase
:
627 value
= value
.lower()
629 if event
.GetKeyCode() == wx
.WXK_UP
:
633 match_index
, partial_match
= self
._autoComplete
(
635 self
._ctrl
_constraints
._compareChoices
,
637 self
._ctrl
_constraints
._compareNoCase
,
638 current_index
= self
._ctrl
_constraints
._autoCompleteIndex
)
639 if match_index
is not None:
640 ## dbg('setting selection to', match_index)
641 # issue appropriate event to outside:
642 self
._OnAutoSelect
(self
._ctrl
_constraints
, match_index
=match_index
)
644 keep_processing
= False
646 pos
= self
._adjustPos
(self
._GetInsertionPoint
(), event
.GetKeyCode())
647 field
= self
._FindField
(pos
)
648 if self
.IsEmpty() or not field
._hasList
:
649 ## dbg('selecting 1st value in list')
650 self
._OnAutoSelect
(self
._ctrl
_constraints
, match_index
=0)
652 keep_processing
= False
654 # attempt field-level auto-complete
656 keep_processing
= self
._OnAutoCompleteField
(event
)
657 ## dbg('keep processing?', keep_processing, indent=0)
658 return keep_processing
661 def _OnAutoSelect(self
, field
, match_index
):
663 Override mixin (empty) autocomplete handler, so that autocompletion causes
664 combobox to update appropriately.
666 ## dbg('MaskedComboBox::OnAutoSelect(%d, %d)' % (field._index, match_index), indent=1)
667 ## field._autoCompleteIndex = match_index
668 if field
== self
._ctrl
_constraints
:
669 self
.SetSelection(match_index
)
670 ## dbg('issuing combo selection event')
671 self
.GetEventHandler().ProcessEvent(
672 MaskedComboBoxSelectEvent( self
.GetId(), match_index
, self
) )
674 ## dbg('field._autoCompleteIndex:', match_index)
675 ## dbg('self.GetCurrentSelection():', self.GetCurrentSelection())
676 end
= self
._goEnd
(getPosOnly
=True)
677 ## dbg('scheduling set of end position to:', end)
678 # work around bug in wx 2.5
679 wx
.CallAfter(self
.SetInsertionPoint
, 0)
680 wx
.CallAfter(self
.SetInsertionPoint
, end
)
684 def _OnReturn(self
, event
):
686 For wx.ComboBox, it seems that if you hit return when the dropdown is
687 dropped, the event that dismisses the dropdown will also blank the
688 control, because of the implementation of wxComboBox. So this function
689 examines the selection and if it is -1, and the value according to
690 (the base control!) is a value in the list, then it schedules a
691 programmatic wxComboBox.SetSelection() call to pick the appropriate
692 item in the list. (and then does the usual OnReturn bit.)
694 ## dbg('MaskedComboBox::OnReturn', indent=1)
695 ## dbg('current value: "%s"' % self.GetValue(), 'current selection:', self.GetCurrentSelection())
696 if self
.GetCurrentSelection() == -1 and self
.GetValue().lower().strip() in self
._ctrl
_constraints
._compareChoices
:
697 ## dbg('attempting to correct the selection to make it %d' % self._ctrl_constraints._autoCompleteIndex)
698 ## wx.CallAfter(self.SetSelection, self._ctrl_constraints._autoCompleteIndex)
699 self
.replace_next_combobox_event
= True
700 self
.correct_selection
= self
._ctrl
_constraints
._autoCompleteIndex
701 event
.m_keyCode
= wx
.WXK_TAB
706 def _LostFocus(self
):
707 ## dbg('MaskedComboBox::LostFocus; Selection=%d, value="%s"' % (self.GetSelection(), self.GetValue()))
708 if self
.GetCurrentSelection() == -1 and self
.GetValue().lower().strip() in self
._ctrl
_constraints
._compareChoices
:
709 ## dbg('attempting to correct the selection to make it %d' % self._ctrl_constraints._autoCompleteIndex)
710 wx
.CallAfter(self
.SetSelection
, self
._ctrl
_constraints
._autoCompleteIndex
)
713 class ComboBox( BaseMaskedComboBox
, MaskedEditAccessorsMixin
):
715 The "user-visible" masked combobox control, this class is
716 identical to the BaseMaskedComboBox class it's derived from.
717 (This extra level of inheritance allows us to add the generic
718 set of masked edit parameters only to this class while allowing
719 other classes to derive from the "base" masked combobox control,
720 and provide a smaller set of valid accessor functions.)
721 See BaseMaskedComboBox for available methods.
726 class PreMaskedComboBox( BaseMaskedComboBox
, MaskedEditAccessorsMixin
):
728 This class exists to support the use of XRC subclassing.
730 # This should really be wx.EVT_WINDOW_CREATE but it is not
731 # currently delivered for native controls on all platforms, so
732 # we'll use EVT_SIZE instead. It should happen shortly after the
733 # control is created as the control is set to its "best" size.
734 _firstEventType
= wx
.EVT_SIZE
737 pre
= wx
.PreComboBox()
739 self
.Bind(self
._firstEventType
, self
.OnCreate
)
742 def OnCreate(self
, evt
):
743 self
.Unbind(self
._firstEventType
)
748 ## ====================
750 ## 1. Added handler for EVT_COMBOBOX to address apparently inconsistent behavior
751 ## of control when the dropdown control is used to do a selection.
752 ## NOTE: due to misbehavior of wx.ComboBox re: losing all concept of the
753 ## current selection index if SetInsertionPoint() is called, which is required
754 ## to support masked .SetValue(), this control is flaky about retaining selection
755 ## information. I can't truly fix this without major changes to the base control,
756 ## but I've tried to compensate as best I can.
757 ## TODO: investigate replacing base control with ComboCtrl instead...
758 ## 2. Fixed navigation in readonly masked combobox, which was not working because
759 ## the base control doesn't do navigation if style=CB_READONLY|WANTS_CHARS.
763 ## 1. Made definition of "hack" GetMark conditional on base class not
764 ## implementing it properly, to allow for migration in wx code base
765 ## while taking advantage of improvements therein for some platforms.
768 ## 1. Converted docstrings to reST format, added doc for ePyDoc.
769 ## 2. Renamed helper functions, vars etc. not intended to be visible in public
770 ## interface to code.
773 ## 1. Added .SetFont() method that properly resizes control
774 ## 2. Modified control to support construction via XRC mechanism.
775 ## 3. Added AppendItems() to conform with latest combobox.