]> git.saurik.com Git - wxWidgets.git/blob - wxPython/wx/lib/masked/combobox.py
Forward port recent changes on the 2.8 branch to HEAD
[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 class MaskedComboBoxEventHandler(wx.EvtHandler):
51 """
52 This handler ensures that the derived control can react to events
53 from the base control before any external handlers run, to ensure
54 proper behavior.
55 """
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
72
73
74
75 class BaseMaskedComboBox( wx.ComboBox, MaskedEditMixin ):
76 """
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.
80 """
81 def __init__( self, parent, id=-1, value = '',
82 pos = wx.DefaultPosition,
83 size = wx.DefaultSize,
84 choices = [],
85 style = wx.CB_DROPDOWN,
86 validator = wx.DefaultValidator,
87 name = "maskedComboBox",
88 setupEventHandling = True, ## setup event handling by default):
89 **kwargs):
90
91
92 kwargs['choices'] = choices ## set up maskededit to work with choice list too
93
94 ## Since combobox completion is case-insensitive, always validate same way
95 if not kwargs.has_key('compareNoCase'):
96 kwargs['compareNoCase'] = True
97
98 MaskedEditMixin.__init__( self, name, **kwargs )
99
100 self._choices = self._ctrl_constraints._choices
101 ## dbg('self._choices:', self._choices)
102
103 if self._ctrl_constraints._alignRight:
104 choices = [choice.rjust(self._masklength) for choice in choices]
105 else:
106 choices = [choice.ljust(self._masklength) for choice in choices]
107
108 wx.ComboBox.__init__(self, parent, id, value='',
109 pos=pos, size = size,
110 choices=choices, style=style|wx.WANTS_CHARS,
111 validator=validator,
112 name=name)
113 self.controlInitialized = True
114
115 self._PostInit(style=style, setupEventHandling=setupEventHandling,
116 name=name, value=value, **kwargs)
117
118
119 def _PostInit(self, style=wx.CB_DROPDOWN,
120 setupEventHandling = True, ## setup event handling by default):
121 name = "maskedComboBox", value='', **kwargs):
122
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
127
128 if not hasattr(self, 'controlInitialized'):
129
130 self.controlInitialized = True ## must have been called via XRC, therefore base class is constructed
131 if not kwargs.has_key('choices'):
132 choices=[]
133 kwargs['choices'] = choices ## set up maskededit to work with choice list too
134 self._choices = []
135
136 ## Since combobox completion is case-insensitive, always validate same way
137 if not kwargs.has_key('compareNoCase'):
138 kwargs['compareNoCase'] = True
139
140 MaskedEditMixin.__init__( self, name, **kwargs )
141
142 self._choices = self._ctrl_constraints._choices
143 ## dbg('self._choices:', self._choices)
144
145 if self._ctrl_constraints._alignRight:
146 choices = [choice.rjust(self._masklength) for choice in choices]
147 else:
148 choices = [choice.ljust(self._masklength) for choice in choices]
149 wx.ComboBox.Clear(self)
150 wx.ComboBox.AppendItems(self, choices)
151
152
153 # Set control font - fixed width by default
154 self._setFont()
155
156 if self._autofit:
157 self.SetClientSize(self._CalcSize())
158 width = self.GetSize().width
159 height = self.GetBestSize().height
160 self.SetInitialSize((width, height))
161
162
163 if value:
164 # ensure value is width of the mask of the control:
165 if self._ctrl_constraints._alignRight:
166 value = value.rjust(self._masklength)
167 else:
168 value = value.ljust(self._masklength)
169
170 if self.__readonly:
171 self.SetStringSelection(value)
172 else:
173 self._SetInitialValue(value)
174
175
176 self._SetKeycodeHandler(wx.WXK_UP, self._OnSelectChoice)
177 self._SetKeycodeHandler(wx.WXK_DOWN, self._OnSelectChoice)
178
179 self.replace_next_combobox_event = False
180 self.correct_selection = -1
181
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 )
188
189
190
191 def __repr__(self):
192 return "<MaskedComboBox: %s>" % self.GetValue()
193
194
195 def OnWindowDestroy(self, event):
196 # clean up associated event handler object:
197 if self.RemoveEventHandler(self.evt_handler):
198 self.evt_handler.Destroy()
199 event.Skip()
200
201
202 def _CalcSize(self, size=None):
203 """
204 Calculate automatic size if allowed; augment base mixin function
205 to account for the selector button.
206 """
207 size = self._calcSize(size)
208 return (size[0]+20, size[1])
209
210
211 def SetFont(self, *args, **kwargs):
212 """ Set the font, then recalculate control size, if appropriate. """
213 wx.ComboBox.SetFont(self, *args, **kwargs)
214 if self._autofit:
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))
221
222
223 def _GetSelection(self):
224 """
225 Allow mixin to get the text selection of this control.
226 REQUIRED by any class derived from MaskedEditMixin.
227 """
228 ## dbg('MaskedComboBox::_GetSelection()')
229 return self.GetMark()
230
231 def _SetSelection(self, sel_start, sel_to):
232 """
233 Allow mixin to set the text selection of this control.
234 REQUIRED by any class derived from MaskedEditMixin.
235 """
236 ## dbg('MaskedComboBox::_SetSelection: setting mark to (%d, %d)' % (sel_start, sel_to))
237 return self.SetMark( sel_start, sel_to )
238
239
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)
248 return ret
249
250 def _SetInsertionPoint(self, pos):
251 ## dbg('MaskedComboBox::_SetInsertionPoint(%d)' % pos)
252 self.SetInsertionPoint(pos)
253
254
255 def IsEmpty(*args, **kw):
256 return MaskedEditMixin.IsEmpty(*args, **kw)
257
258
259 def _GetValue(self):
260 """
261 Allow mixin to get the raw value of the control with this function.
262 REQUIRED by any class derived from MaskedEditMixin.
263 """
264 return self.GetValue()
265
266 def _SetValue(self, value):
267 """
268 Allow mixin to set the raw value of the control with this function.
269 REQUIRED by any class derived from MaskedEditMixin.
270 """
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
274 # to match the mask:
275 if self._ctrl_constraints._alignRight:
276 value = value.rjust(self._masklength)
277 else:
278 value = value.ljust(self._masklength)
279
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:
288 self._CheckValid()
289
290 def SetValue(self, value):
291 """
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.
296 """
297 ## dbg('MaskedComboBox::SetValue(%s)' % value, indent=1)
298 if not self._mask:
299 wx.ComboBox.SetValue(value) # revert to base control behavior
300 ## dbg('no mask; deferring to base class', indent=0)
301 return
302 # else...
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)
307
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)
314
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
318 # to match the mask:
319 elif self._ctrl_constraints._alignRight:
320 value = value.rjust(self._masklength)
321 else:
322 value = value.ljust(self._masklength)
323
324
325 # make SetValue behave the same as if you had typed the value in:
326 try:
327 value, replace_to = self._Paste(value, raise_on_invalid=True, just_return_value=True)
328 if self._isFloat:
329 self._isNeg = False # (clear current assumptions)
330 value = self._adjustFloat(value)
331 elif self._isInt:
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)
336 except ValueError:
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)
343 else:
344 raise
345 ## dbg('adjusted value: "%s"' % value)
346
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)
356 else:
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)
361 ## dbg(indent=0)
362
363
364 def _Refresh(self):
365 """
366 Allow mixin to refresh the base control with this function.
367 REQUIRED by any class derived from MaskedEditMixin.
368 """
369 wx.ComboBox.Refresh(self)
370
371 def Refresh(self):
372 """
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.
376 """
377 self._CheckValid()
378 self._Refresh()
379
380
381 def _IsEditable(self):
382 """
383 Allow mixin to determine if the base control is editable with this function.
384 REQUIRED by any class derived from MaskedEditMixin.
385 """
386 return not self.__readonly
387
388
389 def Cut(self):
390 """
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.
395 """
396 if self._mask:
397 self._Cut() # call the mixin's Cut method
398 else:
399 wx.ComboBox.Cut(self) # else revert to base control behavior
400
401
402 def Paste(self):
403 """
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.
408 """
409 if self._mask:
410 self._Paste() # call the mixin's Paste method
411 else:
412 wx.ComboBox.Paste(self) # else revert to base control behavior
413
414
415 def Undo(self):
416 """
417 This function defines the undo operation for the control. (The default
418 undo is 1-deep.)
419 """
420 if self._mask:
421 self._Undo()
422 else:
423 wx.ComboBox.Undo() # else revert to base control behavior
424
425 def Append( self, choice, clientData=None ):
426 """
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.
432 """
433 if self._mask:
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))
438
439 if not self._ctrl_constraints._choices:
440 self._ctrl_constraints._compareChoices = []
441 self._ctrl_constraints._choices = []
442 self._hasList = True
443
444 compareChoice = choice.strip()
445
446 if self._ctrl_constraints._compareNoCase:
447 compareChoice = compareChoice.lower()
448
449 if self._ctrl_constraints._alignRight:
450 choice = choice.rjust(self._masklength)
451 else:
452 choice = choice.ljust(self._masklength)
453 if self._ctrl_constraints._fillChar != ' ':
454 choice = choice.replace(' ', self._fillChar)
455 ## dbg('updated choice:', choice)
456
457
458 self._ctrl_constraints._compareChoices.append(compareChoice)
459 self._ctrl_constraints._choices.append(choice)
460 self._choices = self._ctrl_constraints._choices # (for shorthand)
461
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))
466
467 wx.ComboBox.Append(self, choice, clientData)
468
469
470 def AppendItems( self, choices ):
471 """
472 AppendItems() is handled in terms of Append, to avoid code replication.
473 """
474 for choice in choices:
475 self.Append(choice)
476
477
478 def Clear( self ):
479 """
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.
483 """
484 if self._mask:
485 self._choices = []
486 self._ctrl_constraints._autoCompleteIndex = -1
487 if self._ctrl_constraints._choices:
488 self.SetCtrlParameters(choices=[])
489 wx.ComboBox.Clear(self)
490
491
492 def _OnCtrlParametersChanged(self):
493 """
494 This overrides the mixin's default OnCtrlParametersChanged to detect
495 changes in choice list, so masked.Combobox can update the base control:
496 """
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 )
502
503
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...
506 #
507 if not hasattr(wx.ComboBox, 'GetMark'):
508 def GetMark(self):
509 """
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
513 events.
514 """
515 ## dbg(suspend=1) # turn off debugging around this function
516 ## dbg('MaskedComboBox::GetMark', indent=1)
517 if self.__readonly:
518 ## dbg(indent=0)
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)
525
526 self._ignoreChange = True # tell _OnTextChange() to ignore next event (if any)
527
528 wx.ComboBox.Cut(self)
529 newvalue = self.GetValue()
530 ## dbg("value after Cut operation:", newvalue)
531
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)
538
539 self._ignoreChange = False # tell _OnTextChange() to pay attn again
540
541 ## dbg('computed selection:', sel_start, sel_to, indent=0, suspend=0)
542 return sel_start, sel_to
543 else:
544 def GetMark(self):
545 ## dbg('MaskedComboBox::GetMark()', indent = 1)
546 ret = wx.ComboBox.GetMark(self)
547 ## dbg('returned', ret, indent=0)
548 return ret
549
550
551 def SetSelection(self, index):
552 """
553 Necessary override for bookkeeping on choice selection, to keep current value
554 current.
555 """
556 ## dbg('MaskedComboBox::SetSelection(%d)' % index, indent=1)
557 if self._mask:
558 self._prevValue = self._curValue
559 self._ctrl_constraints._autoCompleteIndex = index
560 if index != -1:
561 self._curValue = self._choices[index]
562 else:
563 self._curValue = None
564 wx.ComboBox.SetSelection(self, index)
565 ## dbg('selection now: %d' % self.GetCurrentSelection(), indent=0)
566
567
568 def _OnKeyDownInComboBox(self, event):
569 """
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... {:-( )
574 """
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)
584 # else pass
585 else:
586 ## dbg('calling OnChar()')
587 self._OnChar(event)
588 else:
589 event.Skip() # let mixin default KeyDown behavior occur
590 ## dbg(indent=0)
591
592
593 def _OnDropdownSelect(self, event):
594 """
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
599 fix the problem.
600 """
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)
606 else:
607 ## dbg('skipping EVT_COMBOBOX')
608 event.Skip()
609 ## dbg(indent=0)
610
611
612 def _OnSelectChoice(self, event):
613 """
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.
617 """
618 ## dbg('MaskedComboBox::OnSelectChoice', indent=1)
619
620 if not self._mask:
621 event.Skip()
622 return
623
624 value = self.GetValue().strip()
625
626 if self._ctrl_constraints._compareNoCase:
627 value = value.lower()
628
629 if event.GetKeyCode() == wx.WXK_UP:
630 direction = -1
631 else:
632 direction = 1
633 match_index, partial_match = self._autoComplete(
634 direction,
635 self._ctrl_constraints._compareChoices,
636 value,
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)
643 self._CheckValid()
644 keep_processing = False
645 else:
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)
651 self._CheckValid()
652 keep_processing = False
653 else:
654 # attempt field-level auto-complete
655 ## dbg(indent=0)
656 keep_processing = self._OnAutoCompleteField(event)
657 ## dbg('keep processing?', keep_processing, indent=0)
658 return keep_processing
659
660
661 def _OnAutoSelect(self, field, match_index):
662 """
663 Override mixin (empty) autocomplete handler, so that autocompletion causes
664 combobox to update appropriately.
665 """
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 ) )
673 self._CheckValid()
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)
681 ## dbg(indent=0)
682
683
684 def _OnReturn(self, event):
685 """
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.)
693 """
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
702 event.Skip()
703 ## dbg(indent=0)
704
705
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)
711
712
713 class ComboBox( BaseMaskedComboBox, MaskedEditAccessorsMixin ):
714 """
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.
722 """
723 pass
724
725
726 class PreMaskedComboBox( BaseMaskedComboBox, MaskedEditAccessorsMixin ):
727 """
728 This class exists to support the use of XRC subclassing.
729 """
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
735
736 def __init__(self):
737 pre = wx.PreComboBox()
738 self.PostCreate(pre)
739 self.Bind(self._firstEventType, self.OnCreate)
740
741
742 def OnCreate(self, evt):
743 self.Unbind(self._firstEventType)
744 self._PostInit()
745
746 __i = 0
747 ## CHANGELOG:
748 ## ====================
749 ## Version 1.4
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.
760 ##
761 ##
762 ## Version 1.3
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.
766 ##
767 ## Version 1.2
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.
771 ##
772 ## Version 1.1
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.