masked/textctrl.py: A code refactoring bugfix for .ChangeValue() support.
masked/combobox.py: Several fixes for value selection behavior, and one for navigation in readonly control. git-svn-id: https://svn.wxwidgets.org/svn/wx/wxWidgets/branches/WX_2_8_BRANCH@45968 c3d73ce0-8a6f-49c7-b76d-6d57e0e08775
This commit is contained in:
@@ -47,6 +47,30 @@ class MaskedComboBoxSelectEvent(wx.PyCommandEvent):
|
|||||||
this event was generated."""
|
this event was generated."""
|
||||||
return self.__selection
|
return self.__selection
|
||||||
|
|
||||||
|
class MaskedComboBoxEventHandler(wx.EvtHandler):
|
||||||
|
"""
|
||||||
|
This handler ensures that the derived control can react to events
|
||||||
|
from the base control before any external handlers run, to ensure
|
||||||
|
proper behavior.
|
||||||
|
"""
|
||||||
|
def __init__(self, combobox):
|
||||||
|
wx.EvtHandler.__init__(self)
|
||||||
|
self.combobox = combobox
|
||||||
|
combobox.PushEventHandler(self)
|
||||||
|
self.Bind(wx.EVT_SET_FOCUS, self.combobox._OnFocus ) ## defeat automatic full selection
|
||||||
|
self.Bind(wx.EVT_KILL_FOCUS, self.combobox._OnKillFocus ) ## run internal validator
|
||||||
|
self.Bind(wx.EVT_LEFT_DCLICK, self.combobox._OnDoubleClick) ## select field under cursor on dclick
|
||||||
|
self.Bind(wx.EVT_RIGHT_UP, self.combobox._OnContextMenu ) ## bring up an appropriate context menu
|
||||||
|
self.Bind(wx.EVT_CHAR, self.combobox._OnChar ) ## handle each keypress
|
||||||
|
self.Bind(wx.EVT_KEY_DOWN, self.combobox._OnKeyDownInComboBox ) ## for special processing of up/down keys
|
||||||
|
self.Bind(wx.EVT_KEY_DOWN, self.combobox._OnKeyDown ) ## for processing the rest of the control keys
|
||||||
|
## (next in evt chain)
|
||||||
|
self.Bind(wx.EVT_COMBOBOX, self.combobox._OnDropdownSelect ) ## to bring otherwise completely independent base
|
||||||
|
## ctrl selection into maskededit framework
|
||||||
|
self.Bind(wx.EVT_TEXT, self.combobox._OnTextChange ) ## color control appropriately & keep
|
||||||
|
## track of previous value for undo
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class BaseMaskedComboBox( wx.ComboBox, MaskedEditMixin ):
|
class BaseMaskedComboBox( wx.ComboBox, MaskedEditMixin ):
|
||||||
"""
|
"""
|
||||||
@@ -152,26 +176,25 @@ class BaseMaskedComboBox( wx.ComboBox, MaskedEditMixin ):
|
|||||||
self._SetKeycodeHandler(wx.WXK_UP, self._OnSelectChoice)
|
self._SetKeycodeHandler(wx.WXK_UP, self._OnSelectChoice)
|
||||||
self._SetKeycodeHandler(wx.WXK_DOWN, self._OnSelectChoice)
|
self._SetKeycodeHandler(wx.WXK_DOWN, self._OnSelectChoice)
|
||||||
|
|
||||||
|
self.replace_next_combobox_event = False
|
||||||
|
self.correct_selection = -1
|
||||||
|
|
||||||
if setupEventHandling:
|
if setupEventHandling:
|
||||||
## Setup event handlers
|
## Setup event handling functions through event handler object,
|
||||||
self.Bind(wx.EVT_SET_FOCUS, self._OnFocus ) ## defeat automatic full selection
|
## to guarantee processing prior to giving event callbacks from
|
||||||
self.Bind(wx.EVT_KILL_FOCUS, self._OnKillFocus ) ## run internal validator
|
## outside the class:
|
||||||
self.Bind(wx.EVT_LEFT_DCLICK, self._OnDoubleClick) ## select field under cursor on dclick
|
self.evt_handler = MaskedComboBoxEventHandler(self)
|
||||||
self.Bind(wx.EVT_RIGHT_UP, self._OnContextMenu ) ## bring up an appropriate context menu
|
self.Bind(wx.EVT_WINDOW_DESTROY, self.OnWindowDestroy )
|
||||||
self.Bind(wx.EVT_CHAR, self._OnChar ) ## handle each keypress
|
|
||||||
self.Bind(wx.EVT_KEY_DOWN, self._OnKeyDownInComboBox ) ## for special processing of up/down keys
|
|
||||||
self.Bind(wx.EVT_KEY_DOWN, self._OnKeyDown ) ## for processing the rest of the control keys
|
|
||||||
## (next in evt chain)
|
|
||||||
self.Bind(wx.EVT_COMBOBOX, self._OnDropdownSelect) ## to bring otherwise completely independent base
|
|
||||||
## ctrl selection into maskededit framework
|
|
||||||
self.Bind(wx.EVT_TEXT, self._OnTextChange ) ## color control appropriately & keep
|
|
||||||
## track of previous value for undo
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return "<MaskedComboBox: %s>" % self.GetValue()
|
return "<MaskedComboBox: %s>" % self.GetValue()
|
||||||
|
|
||||||
|
def OnWindowDestroy(self, event):
|
||||||
|
# clean up associated event handler object:
|
||||||
|
self.PopEventHandler(deleteHandler=True)
|
||||||
|
|
||||||
|
|
||||||
def _CalcSize(self, size=None):
|
def _CalcSize(self, size=None):
|
||||||
"""
|
"""
|
||||||
@@ -254,7 +277,9 @@ class BaseMaskedComboBox( wx.ComboBox, MaskedEditMixin ):
|
|||||||
# Record current selection and insertion point, for undo
|
# Record current selection and insertion point, for undo
|
||||||
self._prevSelection = self._GetSelection()
|
self._prevSelection = self._GetSelection()
|
||||||
self._prevInsertionPoint = self._GetInsertionPoint()
|
self._prevInsertionPoint = self._GetInsertionPoint()
|
||||||
|
## dbg('MaskedComboBox::_SetValue(%s), selection beforehand: %d' % (value, self.GetSelection()))
|
||||||
wx.ComboBox.SetValue(self, value)
|
wx.ComboBox.SetValue(self, value)
|
||||||
|
## dbg('MaskedComboBox::_SetValue(%s), selection now: %d' % (value, self.GetSelection()))
|
||||||
# text change events don't always fire, so we check validity here
|
# text change events don't always fire, so we check validity here
|
||||||
# to make certain formatting is applied:
|
# to make certain formatting is applied:
|
||||||
self._CheckValid()
|
self._CheckValid()
|
||||||
@@ -266,11 +291,14 @@ class BaseMaskedComboBox( wx.ComboBox, MaskedEditMixin ):
|
|||||||
masked control. NOTE: this must be done in the class derived
|
masked control. NOTE: this must be done in the class derived
|
||||||
from the base wx control.
|
from the base wx control.
|
||||||
"""
|
"""
|
||||||
|
## dbg('MaskedComboBox::SetValue(%s)' % value, indent=1)
|
||||||
if not self._mask:
|
if not self._mask:
|
||||||
wx.ComboBox.SetValue(value) # revert to base control behavior
|
wx.ComboBox.SetValue(value) # revert to base control behavior
|
||||||
|
## dbg('no mask; deferring to base class', indent=0)
|
||||||
return
|
return
|
||||||
# else...
|
# else...
|
||||||
# empty previous contents, replacing entire value:
|
# empty previous contents, replacing entire value:
|
||||||
|
## dbg('MaskedComboBox::SetValue: selection beforehand: %d' % (self.GetSelection()))
|
||||||
self._SetInsertionPoint(0)
|
self._SetInsertionPoint(0)
|
||||||
self._SetSelection(0, self._masklength)
|
self._SetSelection(0, self._masklength)
|
||||||
|
|
||||||
@@ -308,15 +336,26 @@ class BaseMaskedComboBox( wx.ComboBox, MaskedEditMixin ):
|
|||||||
dateparts = value.split(' ')
|
dateparts = value.split(' ')
|
||||||
dateparts[0] = self._adjustDate(dateparts[0], fixcentury=True)
|
dateparts[0] = self._adjustDate(dateparts[0], fixcentury=True)
|
||||||
value = string.join(dateparts, ' ')
|
value = string.join(dateparts, ' ')
|
||||||
## dbg('adjusted value: "%s"' % value)
|
|
||||||
value = self._Paste(value, raise_on_invalid=True, just_return_value=True)
|
value = self._Paste(value, raise_on_invalid=True, just_return_value=True)
|
||||||
else:
|
else:
|
||||||
raise
|
raise
|
||||||
|
## dbg('adjusted value: "%s"' % value)
|
||||||
|
|
||||||
self._SetValue(value)
|
# Attempt to compensate for fact that calling .SetInsertionPoint() makes the
|
||||||
#### dbg('queuing insertion after .SetValue', replace_to)
|
# selection index -1, even if the resulting set value is in the list.
|
||||||
wx.CallAfter(self._SetInsertionPoint, replace_to)
|
# So, if we are setting a value that's in the list, use index selection instead.
|
||||||
wx.CallAfter(self._SetSelection, replace_to, replace_to)
|
if value in self._choices:
|
||||||
|
index = self._choices.index(value)
|
||||||
|
self._prevValue = self._curValue
|
||||||
|
self._curValue = self._choices[index]
|
||||||
|
self._ctrl_constraints._autoCompleteIndex = index
|
||||||
|
self.SetSelection(index)
|
||||||
|
else:
|
||||||
|
self._SetValue(value)
|
||||||
|
#### dbg('queuing insertion after .SetValue', replace_to)
|
||||||
|
wx.CallAfter(self._SetInsertionPoint, replace_to)
|
||||||
|
wx.CallAfter(self._SetSelection, replace_to, replace_to)
|
||||||
|
## dbg(indent=0)
|
||||||
|
|
||||||
|
|
||||||
def _Refresh(self):
|
def _Refresh(self):
|
||||||
@@ -511,26 +550,41 @@ class BaseMaskedComboBox( wx.ComboBox, MaskedEditMixin ):
|
|||||||
Necessary override for bookkeeping on choice selection, to keep current value
|
Necessary override for bookkeeping on choice selection, to keep current value
|
||||||
current.
|
current.
|
||||||
"""
|
"""
|
||||||
## dbg('MaskedComboBox::SetSelection(%d)' % index)
|
## dbg('MaskedComboBox::SetSelection(%d)' % index, indent=1)
|
||||||
if self._mask:
|
if self._mask:
|
||||||
self._prevValue = self._curValue
|
self._prevValue = self._curValue
|
||||||
self._curValue = self._choices[index]
|
|
||||||
self._ctrl_constraints._autoCompleteIndex = index
|
self._ctrl_constraints._autoCompleteIndex = index
|
||||||
|
if index != -1:
|
||||||
|
self._curValue = self._choices[index]
|
||||||
|
else:
|
||||||
|
self._curValue = None
|
||||||
wx.ComboBox.SetSelection(self, index)
|
wx.ComboBox.SetSelection(self, index)
|
||||||
|
## dbg('selection now: %d' % self.GetCurrentSelection(), indent=0)
|
||||||
|
|
||||||
|
|
||||||
def _OnKeyDownInComboBox(self, event):
|
def _OnKeyDownInComboBox(self, event):
|
||||||
"""
|
"""
|
||||||
This function is necessary because navigation and control key
|
This function is necessary because navigation and control key events
|
||||||
events do not seem to normally be seen by the wxComboBox's
|
do not seem to normally be seen by the wxComboBox's EVT_CHAR routine.
|
||||||
EVT_CHAR routine. (Tabs don't seem to be visible no matter
|
(Tabs don't seem to be visible no matter what, except for CB_READONLY
|
||||||
what... {:-( )
|
controls, for some bizarre reason... {:-( )
|
||||||
"""
|
"""
|
||||||
|
key = event.GetKeyCode()
|
||||||
|
## dbg('MaskedComboBox::OnKeyDownInComboBox(%d)' % key)
|
||||||
if event.GetKeyCode() in self._nav + self._control:
|
if event.GetKeyCode() in self._nav + self._control:
|
||||||
self._OnChar(event)
|
if not self._IsEditable():
|
||||||
return
|
# WANTS_CHARS with CB_READONLY apparently prevents navigation on WXK_TAB;
|
||||||
|
# ensure we can still navigate properly, as maskededit mixin::OnChar assumes
|
||||||
|
# that event.Skip() will just work, but it doesn't:
|
||||||
|
if self._keyhandlers.has_key(key):
|
||||||
|
self._keyhandlers[key](event)
|
||||||
|
# else pass
|
||||||
|
else:
|
||||||
|
## dbg('calling OnChar()')
|
||||||
|
self._OnChar(event)
|
||||||
else:
|
else:
|
||||||
event.Skip() # let mixin default KeyDown behavior occur
|
event.Skip() # let mixin default KeyDown behavior occur
|
||||||
|
## dbg(indent=0)
|
||||||
|
|
||||||
|
|
||||||
def _OnDropdownSelect(self, event):
|
def _OnDropdownSelect(self, event):
|
||||||
@@ -541,8 +595,15 @@ class BaseMaskedComboBox( wx.ComboBox, MaskedEditMixin ):
|
|||||||
on a selection event for the same selection would seem like a nop, but it seems to
|
on a selection event for the same selection would seem like a nop, but it seems to
|
||||||
fix the problem.
|
fix the problem.
|
||||||
"""
|
"""
|
||||||
self.SetSelection(event.GetSelection())
|
## dbg('MaskedComboBox::OnDropdownSelect(%d)' % event.GetSelection(), indent=1)
|
||||||
event.Skip()
|
if self.replace_next_combobox_event:
|
||||||
|
## dbg('replacing EVT_COMBOBOX')
|
||||||
|
self.replace_next_combobox_event = False
|
||||||
|
self._OnAutoSelect(self._ctrl_constraints, self.correct_selection)
|
||||||
|
else:
|
||||||
|
## dbg('skipping EVT_COMBOBOX')
|
||||||
|
event.Skip()
|
||||||
|
## dbg(indent=0)
|
||||||
|
|
||||||
|
|
||||||
def _OnSelectChoice(self, event):
|
def _OnSelectChoice(self, event):
|
||||||
@@ -599,7 +660,7 @@ class BaseMaskedComboBox( wx.ComboBox, MaskedEditMixin ):
|
|||||||
Override mixin (empty) autocomplete handler, so that autocompletion causes
|
Override mixin (empty) autocomplete handler, so that autocompletion causes
|
||||||
combobox to update appropriately.
|
combobox to update appropriately.
|
||||||
"""
|
"""
|
||||||
## dbg('MaskedComboBox::OnAutoSelect', field._index, indent=1)
|
## dbg('MaskedComboBox::OnAutoSelect(%d, %d)' % (field._index, match_index), indent=1)
|
||||||
## field._autoCompleteIndex = match_index
|
## field._autoCompleteIndex = match_index
|
||||||
if field == self._ctrl_constraints:
|
if field == self._ctrl_constraints:
|
||||||
self.SetSelection(match_index)
|
self.SetSelection(match_index)
|
||||||
@@ -608,7 +669,7 @@ class BaseMaskedComboBox( wx.ComboBox, MaskedEditMixin ):
|
|||||||
MaskedComboBoxSelectEvent( self.GetId(), match_index, self ) )
|
MaskedComboBoxSelectEvent( self.GetId(), match_index, self ) )
|
||||||
self._CheckValid()
|
self._CheckValid()
|
||||||
## dbg('field._autoCompleteIndex:', match_index)
|
## dbg('field._autoCompleteIndex:', match_index)
|
||||||
## dbg('self.GetSelection():', self.GetSelection())
|
## dbg('self.GetCurrentSelection():', self.GetCurrentSelection())
|
||||||
end = self._goEnd(getPosOnly=True)
|
end = self._goEnd(getPosOnly=True)
|
||||||
## dbg('scheduling set of end position to:', end)
|
## dbg('scheduling set of end position to:', end)
|
||||||
# work around bug in wx 2.5
|
# work around bug in wx 2.5
|
||||||
@@ -628,15 +689,24 @@ class BaseMaskedComboBox( wx.ComboBox, MaskedEditMixin ):
|
|||||||
item in the list. (and then does the usual OnReturn bit.)
|
item in the list. (and then does the usual OnReturn bit.)
|
||||||
"""
|
"""
|
||||||
## dbg('MaskedComboBox::OnReturn', indent=1)
|
## dbg('MaskedComboBox::OnReturn', indent=1)
|
||||||
## dbg('current value: "%s"' % self.GetValue(), 'current index:', self.GetSelection())
|
## dbg('current value: "%s"' % self.GetValue(), 'current selection:', self.GetCurrentSelection())
|
||||||
if self.GetSelection() == -1 and self.GetValue().lower().strip() in self._ctrl_constraints._compareChoices:
|
if self.GetCurrentSelection() == -1 and self.GetValue().lower().strip() in self._ctrl_constraints._compareChoices:
|
||||||
wx.CallAfter(self.SetSelection, self._ctrl_constraints._autoCompleteIndex)
|
## dbg('attempting to correct the selection to make it %d' % self._ctrl_constraints._autoCompleteIndex)
|
||||||
|
## wx.CallAfter(self.SetSelection, self._ctrl_constraints._autoCompleteIndex)
|
||||||
|
self.replace_next_combobox_event = True
|
||||||
|
self.correct_selection = self._ctrl_constraints._autoCompleteIndex
|
||||||
event.m_keyCode = wx.WXK_TAB
|
event.m_keyCode = wx.WXK_TAB
|
||||||
event.Skip()
|
event.Skip()
|
||||||
## dbg(indent=0)
|
## dbg(indent=0)
|
||||||
|
|
||||||
|
|
||||||
|
def _LostFocus(self):
|
||||||
|
## dbg('MaskedComboBox::LostFocus; Selection=%d, value="%s"' % (self.GetSelection(), self.GetValue()))
|
||||||
|
if self.GetCurrentSelection() == -1 and self.GetValue().lower().strip() in self._ctrl_constraints._compareChoices:
|
||||||
|
## dbg('attempting to correct the selection to make it %d' % self._ctrl_constraints._autoCompleteIndex)
|
||||||
|
wx.CallAfter(self.SetSelection, self._ctrl_constraints._autoCompleteIndex)
|
||||||
|
|
||||||
|
|
||||||
class ComboBox( BaseMaskedComboBox, MaskedEditAccessorsMixin ):
|
class ComboBox( BaseMaskedComboBox, MaskedEditAccessorsMixin ):
|
||||||
"""
|
"""
|
||||||
The "user-visible" masked combobox control, this class is
|
The "user-visible" masked combobox control, this class is
|
||||||
@@ -676,6 +746,15 @@ __i = 0
|
|||||||
## Version 1.4
|
## Version 1.4
|
||||||
## 1. Added handler for EVT_COMBOBOX to address apparently inconsistent behavior
|
## 1. Added handler for EVT_COMBOBOX to address apparently inconsistent behavior
|
||||||
## of control when the dropdown control is used to do a selection.
|
## of control when the dropdown control is used to do a selection.
|
||||||
|
## NOTE: due to misbehavior of wx.ComboBox re: losing all concept of the
|
||||||
|
## current selection index if SetInsertionPoint() is called, which is required
|
||||||
|
## to support masked .SetValue(), this control is flaky about retaining selection
|
||||||
|
## information. I can't truly fix this without major changes to the base control,
|
||||||
|
## but I've tried to compensate as best I can.
|
||||||
|
## TODO: investigate replacing base control with ComboCtrl instead...
|
||||||
|
## 2. Fixed navigation in readonly masked combobox, which was not working because
|
||||||
|
## the base control doesn't do navigation if style=CB_READONLY|WANTS_CHARS.
|
||||||
|
##
|
||||||
##
|
##
|
||||||
## Version 1.3
|
## Version 1.3
|
||||||
## 1. Made definition of "hack" GetMark conditional on base class not
|
## 1. Made definition of "hack" GetMark conditional on base class not
|
||||||
|
@@ -176,15 +176,28 @@ class BaseMaskedTextCtrl( wx.TextCtrl, MaskedEditMixin ):
|
|||||||
wx.TextCtrl.ChangeValue(self, value)
|
wx.TextCtrl.ChangeValue(self, value)
|
||||||
## dbg(indent=0)
|
## dbg(indent=0)
|
||||||
|
|
||||||
|
def SetValue(self, value):
|
||||||
def SetValue(self, value, use_change_value=False):
|
|
||||||
"""
|
"""
|
||||||
This function redefines the externally accessible .SetValue() to be
|
This function redefines the externally accessible .SetValue() to be
|
||||||
a smart "paste" of the text in question, so as not to corrupt the
|
a smart "paste" of the text in question, so as not to corrupt the
|
||||||
masked control. NOTE: this must be done in the class derived
|
masked control. NOTE: this must be done in the class derived
|
||||||
from the base wx control.
|
from the base wx control.
|
||||||
"""
|
"""
|
||||||
## dbg('MaskedTextCtrl::SetValue("%(value)s", use_change_value=%(use_change_value)d)' % locals(), indent=1)
|
self.ModifyValue(value, use_change_value=False)
|
||||||
|
|
||||||
|
def ChangeValue(self, value):
|
||||||
|
"""
|
||||||
|
Provided to accomodate similar functionality added to base control in wxPython 2.7.1.1.
|
||||||
|
"""
|
||||||
|
self.ModifyValue(value, use_change_value=True)
|
||||||
|
|
||||||
|
|
||||||
|
def ModifyValue(self, value, use_change_value=False):
|
||||||
|
"""
|
||||||
|
This factored function of common code does the bulk of the work for SetValue
|
||||||
|
and ChangeValue.
|
||||||
|
"""
|
||||||
|
## dbg('MaskedTextCtrl::ModifyValue("%(value)s", use_change_value=%(use_change_value)d)' % locals(), indent=1)
|
||||||
|
|
||||||
if not self._mask:
|
if not self._mask:
|
||||||
if use_change_value:
|
if use_change_value:
|
||||||
@@ -214,7 +227,7 @@ class BaseMaskedTextCtrl( wx.TextCtrl, MaskedEditMixin ):
|
|||||||
value = value[1:]
|
value = value[1:]
|
||||||
## dbg('padded value = "%s"' % value)
|
## dbg('padded value = "%s"' % value)
|
||||||
|
|
||||||
# make SetValue behave the same as if you had typed the value in:
|
# make Set/ChangeValue behave the same as if you had typed the value in:
|
||||||
try:
|
try:
|
||||||
value, replace_to = self._Paste(value, raise_on_invalid=True, just_return_value=True)
|
value, replace_to = self._Paste(value, raise_on_invalid=True, just_return_value=True)
|
||||||
if self._isFloat:
|
if self._isFloat:
|
||||||
@@ -247,12 +260,6 @@ class BaseMaskedTextCtrl( wx.TextCtrl, MaskedEditMixin ):
|
|||||||
wx.CallAfter(self._SetSelection, replace_to, replace_to)
|
wx.CallAfter(self._SetSelection, replace_to, replace_to)
|
||||||
## dbg(indent=0)
|
## dbg(indent=0)
|
||||||
|
|
||||||
def ChangeValue(self, value):
|
|
||||||
"""
|
|
||||||
Provided to accomodate similar functionality added to base control in wxPython 2.7.1.1.
|
|
||||||
"""
|
|
||||||
self.SetValue(value, use_change_value=True)
|
|
||||||
|
|
||||||
|
|
||||||
def SetFont(self, *args, **kwargs):
|
def SetFont(self, *args, **kwargs):
|
||||||
""" Set the font, then recalculate control size, if appropriate. """
|
""" Set the font, then recalculate control size, if appropriate. """
|
||||||
|
Reference in New Issue
Block a user