Patch from Will Sadkin:

- Fixed intra-right-insert-field erase.
- Allowed right-insert in ipaddrctrl subfields.
- Made _SetValue() place cursor after last non-blank character inserted,
  rather than end of mask.
- Fixed combobox autoselect behavior to work similarly as above, so that
  said selection will only select the non-empty text, as per request.
- Fixed some incorrect undo behavior for right-insert fields
- Allowed derived classes (eg. numctrl) to pass modified values for undo
  processing (to handle/ignore grouping chars properly.)


git-svn-id: https://svn.wxwidgets.org/svn/wx/wxWidgets/trunk@27898 c3d73ce0-8a6f-49c7-b76d-6d57e0e08775
This commit is contained in:
Robin Dunn
2004-06-19 16:25:55 +00:00
parent f8167d6ee2
commit 5f280eaa57
5 changed files with 308 additions and 58 deletions

View File

@@ -219,7 +219,7 @@ class BaseMaskedComboBox( wx.ComboBox, MaskedEditMixin ):
# make SetValue behave the same as if you had typed the value in: # make SetValue behave the same as if you had typed the value in:
try: try:
value = 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:
self._isNeg = False # (clear current assumptions) self._isNeg = False # (clear current assumptions)
value = self._adjustFloat(value) value = self._adjustFloat(value)
@@ -240,9 +240,9 @@ class BaseMaskedComboBox( wx.ComboBox, MaskedEditMixin ):
raise raise
self._SetValue(value) self._SetValue(value)
#### dbg('queuing insertion after .SetValue', self._masklength) #### dbg('queuing insertion after .SetValue', replace_to)
wx.CallAfter(self._SetInsertionPoint, self._masklength) wx.CallAfter(self._SetInsertionPoint, replace_to)
wx.CallAfter(self._SetSelection, self._masklength, self._masklength) wx.CallAfter(self._SetSelection, replace_to, replace_to)
def _Refresh(self): def _Refresh(self):
@@ -506,6 +506,11 @@ class BaseMaskedComboBox( wx.ComboBox, MaskedEditMixin ):
self._CheckValid() self._CheckValid()
## dbg('field._autoCompleteIndex:', match_index) ## dbg('field._autoCompleteIndex:', match_index)
## dbg('self.GetSelection():', self.GetSelection()) ## dbg('self.GetSelection():', self.GetSelection())
end = self._goEnd(getPosOnly=True)
## dbg('scheduling set of end position to:', end)
# work around bug in wx 2.5
wx.CallAfter(self.SetInsertionPoint, 0)
wx.CallAfter(self.SetInsertionPoint, end)
## dbg(indent=0) ## dbg(indent=0)

View File

@@ -83,7 +83,7 @@ class IpAddrCtrl( BaseMaskedTextCtrl, IpAddrCtrlAccessorsMixin ):
if not kwargs.has_key('mask'): if not kwargs.has_key('mask'):
kwargs['mask'] = mask = "###.###.###.###" kwargs['mask'] = mask = "###.###.###.###"
if not kwargs.has_key('formatcodes'): if not kwargs.has_key('formatcodes'):
kwargs['formatcodes'] = 'F_Sr<' kwargs['formatcodes'] = 'F_Sr<>'
if not kwargs.has_key('validRegex'): if not kwargs.has_key('validRegex'):
kwargs['validRegex'] = "( \d| \d\d|(1\d\d|2[0-4]\d|25[0-5]))(\.( \d| \d\d|(1\d\d|2[0-4]\d|25[0-5]))){3}" kwargs['validRegex'] = "( \d| \d\d|(1\d\d|2[0-4]\d|25[0-5]))(\.( \d| \d\d|(1\d\d|2[0-4]\d|25[0-5]))){3}"
@@ -184,4 +184,6 @@ class IpAddrCtrl( BaseMaskedTextCtrl, IpAddrCtrlAccessorsMixin ):
BaseMaskedTextCtrl.SetValue(self, value) BaseMaskedTextCtrl.SetValue(self, value)
## dbg(indent=0) ## dbg(indent=0)
i=0
## Version 1.1
## Made ipaddrctrls allow right-insert in subfields, now that insert/cut/paste works better

View File

@@ -3098,7 +3098,7 @@ class MaskedEditMixin:
return False return False
def _OnErase(self, event=None): def _OnErase(self, event=None, just_return_value=False):
""" Handles backspace and delete keypress in control. Should return False to skip other processing.""" """ Handles backspace and delete keypress in control. Should return False to skip other processing."""
## dbg("MaskedEditMixin::_OnErase", indent=1) ## dbg("MaskedEditMixin::_OnErase", indent=1)
sel_start, sel_to = self._GetSelection() ## check for a range of selected text sel_start, sel_to = self._GetSelection() ## check for a range of selected text
@@ -3316,6 +3316,11 @@ class MaskedEditMixin:
## dbg(indent=0) ## dbg(indent=0)
return False return False
if just_return_value:
## dbg(indent=0)
return newstr
# else...
## dbg('setting value (later) to', newstr) ## dbg('setting value (later) to', newstr)
wx.CallAfter(self._SetValue, newstr) wx.CallAfter(self._SetValue, newstr)
## dbg('setting insertion point (later) to', pos) ## dbg('setting insertion point (later) to', pos)
@@ -4710,11 +4715,28 @@ class MaskedEditMixin:
newtext = "" newtext = ""
newpos = pos newpos = pos
# if >= 2 chars selected in a right-insert field, do appropriate erase on field,
# then set selection to end, and do usual right insert.
if sel_start != sel_to and sel_to >= sel_start+2:
field = self._FindField(sel_start)
if( field._insertRight # if right-insert
and field._allowInsert # and allow insert at any point in field
and field == self._FindField(sel_to) ): # and selection all in same field
text = self._OnErase(just_return_value=True) # remove selection before insert
## dbg('text after (left)erase: "%s"' % text)
pos = sel_start = sel_to
if pos != sel_start and sel_start == sel_to: if pos != sel_start and sel_start == sel_to:
# adjustpos must have moved the position; make selection match: # adjustpos must have moved the position; make selection match:
sel_start = sel_to = pos sel_start = sel_to = pos
## dbg('field._insertRight?', field._insertRight) ## dbg('field._insertRight?', field._insertRight)
## dbg('field._allowInsert?', field._allowInsert)
## dbg('sel_start, end', sel_start, end)
if sel_start < end:
## dbg('text[sel_start] != field._fillChar?', text[sel_start] != field._fillChar)
pass
if( field._insertRight # field allows right insert if( field._insertRight # field allows right insert
and ((sel_start, sel_to) == field._extent # and whole field selected and ((sel_start, sel_to) == field._extent # and whole field selected
or (sel_start == sel_to # or nothing selected or (sel_start == sel_to # or nothing selected
@@ -4870,7 +4892,8 @@ class MaskedEditMixin:
if match_index is not None and partial_match: if match_index is not None and partial_match:
matched_str = newtext matched_str = newtext
newtext = self._ctrl_constraints._choices[match_index] newtext = self._ctrl_constraints._choices[match_index]
new_select_to = self._ctrl_constraints._extent[1] edit_end = self._ctrl_constraints._extent[1]
new_select_to = min(edit_end, len(newvalue.rstrip()))
match_field = self._ctrl_constraints match_field = self._ctrl_constraints
if self._ctrl_constraints._insertRight: if self._ctrl_constraints._insertRight:
# adjust position to just after partial match in control: # adjust position to just after partial match in control:
@@ -5423,11 +5446,31 @@ class MaskedEditMixin:
field = self._FindField(sel_start) field = self._FindField(sel_start)
edit_start, edit_end = field._extent edit_start, edit_end = field._extent
new_pos = None new_pos = None
if field._allowInsert and sel_to <= edit_end and sel_start + len(paste_text) < edit_end: if field._allowInsert and sel_to <= edit_end and (sel_start + len(paste_text) < edit_end or field._insertRight):
if field._insertRight:
# want to paste to the left; see if it will fit:
left_text = self._GetValue()[edit_start:sel_start].lstrip()
## dbg('len(left_text):', len(left_text))
## dbg('len(paste_text):', len(paste_text))
## dbg('sel_start - (len(left_text) + len(paste_text)) >= edit_start?', sel_start - (len(left_text) + len(paste_text)) >= edit_start)
if sel_start - (len(left_text) - (sel_to - sel_start) + len(paste_text)) >= edit_start:
# will fit! create effective paste text, and move cursor back to do so:
paste_text = left_text + paste_text
sel_start -= len(left_text)
paste_text = paste_text.rjust(sel_to - sel_start)
## dbg('modified paste_text to be: "%s"' % paste_text)
## dbg('modified selection to:', (sel_start, sel_to))
else:
## dbg("won't fit left;", 'paste text remains: "%s"' % paste_text)
pass
else:
paste_text = paste_text + self._GetValue()[sel_to:edit_end].rstrip()
## dbg("allow insert, but not insert right;", 'paste text set to: "%s"' % paste_text)
new_pos = sel_start + len(paste_text) # store for subsequent positioning new_pos = sel_start + len(paste_text) # store for subsequent positioning
paste_text = paste_text + self._GetValue()[sel_to:edit_end].rstrip()
## dbg('paste within insertable field; adjusted paste_text: "%s"' % paste_text, 'end:', edit_end) ## dbg('paste within insertable field; adjusted paste_text: "%s"' % paste_text, 'end:', edit_end)
sel_to = sel_start + len(paste_text) ## dbg('expanded selection to:', (sel_start, sel_to))
# Another special case: paste won't fit, but it's a right-insert field where entire # Another special case: paste won't fit, but it's a right-insert field where entire
# non-empty value is selected, and there's room if the selection is expanded leftward: # non-empty value is selected, and there's room if the selection is expanded leftward:
@@ -5490,7 +5533,7 @@ class MaskedEditMixin:
if not wx.Validator_IsSilent(): if not wx.Validator_IsSilent():
wx.Bell() wx.Bell()
## dbg(indent=0) ## dbg(indent=0)
return False return None, -1
# else... # else...
text = self._eraseSelection() text = self._eraseSelection()
@@ -5521,17 +5564,19 @@ class MaskedEditMixin:
wx.CallAfter(self._SetInsertionPoint, new_pos) wx.CallAfter(self._SetInsertionPoint, new_pos)
else: else:
## dbg(indent=0) ## dbg(indent=0)
return new_text return new_text, replace_to
elif just_return_value: elif just_return_value:
## dbg(indent=0) ## dbg(indent=0)
return self._GetValue() return self._GetValue(), sel_to
## dbg(indent=0) ## dbg(indent=0)
def _Undo(self): def _Undo(self, value=None, prev=None, just_return_results=False):
""" Provides an Undo() method in base controls. """ """ Provides an Undo() method in base controls. """
## dbg("MaskedEditMixin::_Undo", indent=1) ## dbg("MaskedEditMixin::_Undo", indent=1)
value = self._GetValue() if value is None:
prev = self._prevValue value = self._GetValue()
if prev is None:
prev = self._prevValue
## dbg('current value: "%s"' % value) ## dbg('current value: "%s"' % value)
## dbg('previous value: "%s"' % prev) ## dbg('previous value: "%s"' % prev)
if prev is None: if prev is None:
@@ -5588,6 +5633,7 @@ class MaskedEditMixin:
for next_op in range(len(code_5tuples)-1, -1, -1): for next_op in range(len(code_5tuples)-1, -1, -1):
op, i1, i2, j1, j2 = code_5tuples[next_op] op, i1, i2, j1, j2 = code_5tuples[next_op]
## dbg('value[i1:i2]: "%s"' % value[i1:i2], 'template[i1:i2] "%s"' % self._template[i1:i2]) ## dbg('value[i1:i2]: "%s"' % value[i1:i2], 'template[i1:i2] "%s"' % self._template[i1:i2])
field = self._FindField(i2)
if op == 'insert' and prev[j1:j2] != self._template[j1:j2]: if op == 'insert' and prev[j1:j2] != self._template[j1:j2]:
## dbg('insert found: selection =>', (j1, j2)) ## dbg('insert found: selection =>', (j1, j2))
sel_start = j1 sel_start = j1
@@ -5595,9 +5641,8 @@ class MaskedEditMixin:
diff_found = True diff_found = True
break break
elif op == 'delete' and value[i1:i2] != self._template[i1:i2]: elif op == 'delete' and value[i1:i2] != self._template[i1:i2]:
field = self._FindField(i2)
edit_start, edit_end = field._extent edit_start, edit_end = field._extent
if field._insertRight and i2 == edit_end: if field._insertRight and (field._allowInsert or i2 == edit_end):
sel_start = i2 sel_start = i2
sel_to = i2 sel_to = i2
else: else:
@@ -5607,23 +5652,39 @@ class MaskedEditMixin:
diff_found = True diff_found = True
break break
elif op == 'replace': elif op == 'replace':
## dbg('replace found: selection =>', (j1, j2)) if not prev[i1:i2].strip() and field._insertRight:
sel_start = j1 sel_start = sel_to = j2
sel_to = j2 else:
sel_start = j1
sel_to = j2
## dbg('replace found: selection =>', (sel_start, sel_to))
diff_found = True diff_found = True
break break
if diff_found: if diff_found:
# now go forwards, looking for earlier changes: # now go forwards, looking for earlier changes:
## dbg('searching forward...')
for next_op in range(len(code_5tuples)): for next_op in range(len(code_5tuples)):
op, i1, i2, j1, j2 = code_5tuples[next_op] op, i1, i2, j1, j2 = code_5tuples[next_op]
field = self._FindField(i1) field = self._FindField(i1)
if op == 'equal': if op == 'equal':
continue continue
elif op == 'replace': elif op == 'replace':
## dbg('setting sel_start to', i1) if field._insertRight:
sel_start = i1 # if replace with spaces in an insert-right control, ignore "forward" replace
if not prev[i1:i2].strip():
continue
elif j1 < i1:
## dbg('setting sel_start to', j1)
sel_start = j1
else:
## dbg('setting sel_start to', i1)
sel_start = i1
else:
## dbg('setting sel_start to', i1)
sel_start = i1
## dbg('saw replace; breaking')
break break
elif op == 'insert' and not value[i1:i2]: elif op == 'insert' and not value[i1:i2]:
## dbg('forward %s found' % op) ## dbg('forward %s found' % op)
@@ -5635,9 +5696,21 @@ class MaskedEditMixin:
## dbg('setting sel_start to inserted space:', j1) ## dbg('setting sel_start to inserted space:', j1)
sel_start = j1 sel_start = j1
break break
elif op == 'delete' and field._insertRight and not value[i1:i2].lstrip(): elif op == 'delete':
continue ## dbg('delete; field._insertRight?', field._insertRight, 'value[%d:%d].lstrip: "%s"' % (i1,i2,value[i1:i2].lstrip()))
if field._insertRight:
if value[i1:i2].lstrip():
## dbg('setting sel_start to ', j1)
sel_start = j1
## dbg('breaking loop')
break
else:
continue
else:
## dbg('saw delete; breaking')
break
else: else:
## dbg('unknown code!')
# we've got what we need # we've got what we need
break break
@@ -5691,15 +5764,22 @@ class MaskedEditMixin:
prev_sel_start, prev_sel_to = self._prevSelection prev_sel_start, prev_sel_to = self._prevSelection
field = self._FindField(sel_start) field = self._FindField(sel_start)
if( self._signOk
if self._signOk and (self._prevValue[sel_start] in ('-', '(', ')') and sel_start < self._masklength
or self._curValue[sel_start] in ('-', '(', ')')): and (prev[sel_start] in ('-', '(', ')')
or value[sel_start] in ('-', '(', ')')) ):
# change of sign; leave cursor alone... # change of sign; leave cursor alone...
## dbg("prev[sel_start] in ('-', '(', ')')?", prev[sel_start] in ('-', '(', ')'))
## dbg("value[sel_start] in ('-', '(', ')')?", value[sel_start] in ('-', '(', ')'))
## dbg('setting selection to previous one')
sel_start, sel_to = self._prevSelection sel_start, sel_to = self._prevSelection
elif field._groupdigits and (self._curValue[sel_start:sel_to] == field._groupChar elif field._groupdigits and (value[sel_start:sel_to] == field._groupChar
or self._prevValue[sel_start:sel_to] == field._groupChar): or prev[sel_start:sel_to] == field._groupChar):
# do not highlight grouping changes # do not highlight grouping changes
## dbg('value[sel_start:sel_to] == field._groupChar?', value[sel_start:sel_to] == field._groupChar)
## dbg('prev[sel_start:sel_to] == field._groupChar?', prev[sel_start:sel_to] == field._groupChar)
## dbg('setting selection to previous one')
sel_start, sel_to = self._prevSelection sel_start, sel_to = self._prevSelection
else: else:
@@ -5711,7 +5791,13 @@ class MaskedEditMixin:
if prev_select_len >= calc_select_len: if prev_select_len >= calc_select_len:
# old selection was bigger; trust it: # old selection was bigger; trust it:
sel_start, sel_to = self._prevSelection ## dbg('prev_select_len >= calc_select_len?', prev_select_len >= calc_select_len)
if not field._insertRight:
## dbg('setting selection to previous one')
sel_start, sel_to = self._prevSelection
else:
sel_to = self._prevSelection[1]
## dbg('setting selection to', (sel_start, sel_to))
elif( sel_to > prev_sel_to # calculated select past last selection elif( sel_to > prev_sel_to # calculated select past last selection
and prev_sel_to < len(self._template) # and prev_sel_to not at end of control and prev_sel_to < len(self._template) # and prev_sel_to not at end of control
@@ -5742,27 +5828,44 @@ class MaskedEditMixin:
test_sel_start, test_sel_to = prev_sel_start, prev_sel_to test_sel_start, test_sel_to = prev_sel_start, prev_sel_to
## dbg('test selection:', (test_sel_start, test_sel_to)) ## dbg('test selection:', (test_sel_start, test_sel_to))
## dbg('calc change: "%s"' % self._prevValue[sel_start:sel_to]) ## dbg('calc change: "%s"' % prev[sel_start:sel_to])
## dbg('test change: "%s"' % self._prevValue[test_sel_start:test_sel_to]) ## dbg('test change: "%s"' % prev[test_sel_start:test_sel_to])
# if calculated selection spans characters, and same characters # if calculated selection spans characters, and same characters
# "before" the previous insertion point are present there as well, # "before" the previous insertion point are present there as well,
# select the ones related to the last known selection instead. # select the ones related to the last known selection instead.
if( sel_start != sel_to if( sel_start != sel_to
and test_sel_to < len(self._template) and test_sel_to < len(self._template)
and self._prevValue[test_sel_start:test_sel_to] == self._prevValue[sel_start:sel_to] ): and prev[test_sel_start:test_sel_to] == prev[sel_start:sel_to] ):
sel_start, sel_to = test_sel_start, test_sel_to sel_start, sel_to = test_sel_start, test_sel_to
# finally, make sure that the old and new values are
# different where we say they're different:
while( sel_to - 1 > 0
and sel_to > sel_start
and value[sel_to-1:] == prev[sel_to-1:]):
sel_to -= 1
while( sel_start + 1 < self._masklength
and sel_start < sel_to
and value[:sel_start+1] == prev[:sel_start+1]):
sel_start += 1
## dbg('sel_start, sel_to:', sel_start, sel_to) ## dbg('sel_start, sel_to:', sel_start, sel_to)
## dbg('previous value: "%s"' % self._prevValue) ## dbg('previous value: "%s"' % prev)
self._SetValue(self._prevValue) ## dbg(indent=0)
if just_return_results:
return prev, (sel_start, sel_to)
# else...
self._SetValue(prev)
self._SetInsertionPoint(sel_start) self._SetInsertionPoint(sel_start)
self._SetSelection(sel_start, sel_to) self._SetSelection(sel_start, sel_to)
else: else:
## dbg('no difference between previous value') ## dbg('no difference between previous value')
pass ## dbg(indent=0)
## dbg(indent=0) if just_return_results:
return prev, self._GetSelection()
def _OnClear(self, event): def _OnClear(self, event):
@@ -5790,8 +5893,8 @@ class MaskedEditMixin:
wx.EVT_MENU(menu, wx.ID_SELECTALL, self._OnCtrl_A) wx.EVT_MENU(menu, wx.ID_SELECTALL, self._OnCtrl_A)
# ## WSS: The base control apparently handles # ## WSS: The base control apparently handles
# enable/disable of wID_CUT, wxID_COPY, wxID_PASTE # enable/disable of wx.ID_CUT, wx.ID_COPY, wx.ID_PASTE
# and wxID_CLEAR menu items even if the menu is one # and wx.ID_CLEAR menu items even if the menu is one
# we created. However, it doesn't do undo properly, # we created. However, it doesn't do undo properly,
# so we're keeping track of previous values ourselves. # so we're keeping track of previous values ourselves.
# Therefore, we have to override the default update for # Therefore, we have to override the default update for
@@ -6283,6 +6386,17 @@ i=1
## CHANGELOG: ## CHANGELOG:
## ==================== ## ====================
## Version 1.7
## 1. Fixed intra-right-insert-field erase, such that it doesn't leave a hole, but instead
## shifts the text to the left accordingly.
## 2. Fixed _SetValue() to place cursor after last character inserted, rather than end of
## mask.
## 3. Fixed some incorrect undo behavior for right-insert fields, and allowed derived classes
## (eg. numctrl) to pass modified values for undo processing (to handle/ignore grouping
## chars properly.)
## 4. Fixed autoselect behavior to work similarly to (2) above, so that combobox
## selection will only select the non-empty text, as per request.
##
## Version 1.6 ## Version 1.6
## 1. Reorganized masked controls into separate package, renamed things accordingly ## 1. Reorganized masked controls into separate package, renamed things accordingly
## 2. Split actual controls out of this file into their own files. ## 2. Split actual controls out of this file into their own files.

View File

@@ -1050,18 +1050,20 @@ class NumCtrl(BaseMaskedTextCtrl, NumCtrlAccessorsMixin):
## dbg(indent=0) ## dbg(indent=0)
def _OnErase( self, event ): def _OnErase( self, event=None, just_return_value=False ):
""" """
This overrides the base control _OnErase, so that erasing around This overrides the base control _OnErase, so that erasing around
grouping characters auto selects the digit before or after the grouping characters auto selects the digit before or after the
grouping character, so that the erasure does the right thing. grouping character, so that the erasure does the right thing.
""" """
## dbg('NumCtrl::_OnErase', indent=1) ## dbg('NumCtrl::_OnErase', indent=1)
if event is None: # called as action routine from Cut() operation.
key = wx.WXK_DELETE
else:
key = event.GetKeyCode()
#if grouping digits, make sure deletes next to group char always #if grouping digits, make sure deletes next to group char always
# delete next digit to appropriate side: # delete next digit to appropriate side:
if self._groupDigits: if self._groupDigits:
key = event.GetKeyCode()
value = BaseMaskedTextCtrl.GetValue(self) value = BaseMaskedTextCtrl.GetValue(self)
sel_start, sel_to = self._GetSelection() sel_start, sel_to = self._GetSelection()
@@ -1088,7 +1090,7 @@ class NumCtrl(BaseMaskedTextCtrl, NumCtrlAccessorsMixin):
self.SetInsertionPoint(sel_start) self.SetInsertionPoint(sel_start)
self.SetSelection(sel_start, sel_to+1) self.SetSelection(sel_start, sel_to+1)
BaseMaskedTextCtrl._OnErase(self, event) return BaseMaskedTextCtrl._OnErase(self, event, just_return_value)
## dbg(indent=0) ## dbg(indent=0)
@@ -1522,19 +1524,141 @@ class NumCtrl(BaseMaskedTextCtrl, NumCtrlAccessorsMixin):
paste_text = self._getClipboardContents() paste_text = self._getClipboardContents()
else: else:
paste_text = value paste_text = value
# treat paste as "replace number", if appropriate:
sel_start, sel_to = self._GetSelection() sel_start, sel_to = self._GetSelection()
if sel_start == sel_to or self._selectOnEntry and (sel_start, sel_to) == self._fields[0]._extent: orig_sel_start = sel_start
paste_text = self._toGUI(paste_text) orig_sel_to = sel_to
self._SetSelection(0, len(self._mask)) ## dbg('selection:', (sel_start, sel_to))
old_value = self._GetValue()
return MaskedEditMixin._Paste(self, #
field = self._FindField(sel_start)
edit_start, edit_end = field._extent
paste_text = paste_text.replace(self._groupChar, '').replace(self._decimalChar, '.').replace('(', '-').replace(')','')
if field._insertRight and self._groupDigits:
# want to paste to the left; see if it will fit:
left_text = old_value[edit_start:sel_start].lstrip()
## dbg('len(left_text):', len(left_text))
## dbg('len(paste_text):', len(paste_text))
## dbg('sel_start - (len(left_text) + len(paste_text)) >= edit_start?', sel_start - (len(left_text) + len(paste_text)) >= edit_start)
if sel_start - (len(left_text) + len(paste_text)) >= edit_start:
# will fit! create effective paste text, and move cursor back to do so:
paste_text = left_text + paste_text
sel_start -= len(paste_text)
sel_start += sel_to - orig_sel_start # decrease by amount selected
else:
## dbg("won't fit left;", 'paste text remains: "%s"' % paste_text)
## dbg('adjusted start before accounting for grouping:', sel_start)
## dbg('adjusted paste_text before accounting for grouping: "%s"' % paste_text)
pass
if self._groupDigits and sel_start != orig_sel_start:
left_len = len(old_value[:sel_to].lstrip())
# remove group chars from adjusted paste string, and left pad to wipe out
# old characters, so that selection will remove the right chars, and
# readjust will do the right thing:
paste_text = paste_text.replace(self._groupChar,'')
adjcount = left_len - len(paste_text)
paste_text = ' ' * adjcount + paste_text
sel_start = sel_to - len(paste_text)
## dbg('adjusted start after accounting for grouping:', sel_start)
## dbg('adjusted paste_text after accounting for grouping: "%s"' % paste_text)
self.SetInsertionPoint(sel_to)
self.SetSelection(sel_start, sel_to)
## # treat paste as "replace number", if appropriate:
## sel_start, sel_to = self._GetSelection()
## if sel_start == sel_to or self._selectOnEntry and (sel_start, sel_to) == self._fields[0]._extent:
## paste_text = self._toGUI(paste_text)
## self._SetSelection(0, len(self._mask))
new_text, replace_to = MaskedEditMixin._Paste(self,
paste_text, paste_text,
raise_on_invalid=raise_on_invalid, raise_on_invalid=raise_on_invalid,
just_return_value=just_return_value) just_return_value=True)
self._SetInsertionPoint(orig_sel_to)
self._SetSelection(orig_sel_start, orig_sel_to)
if not just_return_value and new_text is not None:
if new_text != self._GetValue():
self.modified = True
if new_text == '':
self.ClearValue()
else:
wx.CallAfter(self._SetValue, new_text)
wx.CallAfter(self._SetInsertionPoint, replace_to)
## dbg(indent=0)
else:
## dbg(indent=0)
return new_text, replace_to
def _Undo(self, value=None, prev=None):
'''numctrl's undo is more complicated than the base control's, due to
grouping characters; we don't want to consider them when calculating
the undone portion.'''
## dbg('NumCtrl::_Undo', indent=1)
if value is None: value = self._GetValue()
if prev is None: prev = self._prevValue
if not self._groupDigits:
ignore, (new_sel_start, new_sel_to) = BaseMaskedTextCtrl._Undo(self, value, prev, just_return_results = True)
self._SetValue(prev)
self._SetInsertionPoint(new_sel_start)
self._SetSelection(new_sel_start, new_sel_to)
self._prevSelection = (new_sel_start, new_sel_to)
## dbg('resetting "prev selection" to', self._prevSelection)
## dbg(indent=0)
return
# else...
sel_start, sel_to = self._prevSelection
edit_start, edit_end = self._FindFieldExtent(0)
adjvalue = self._GetNumValue(value).rjust(self._masklength)
adjprev = self._GetNumValue(prev ).rjust(self._masklength)
# move selection to account for "ungrouped" value:
left_text = value[sel_start:].lstrip()
numleftgroups = len(left_text) - len(left_text.replace(self._groupChar, ''))
adjsel_start = sel_start + numleftgroups
right_text = value[sel_to:].lstrip()
numrightgroups = len(right_text) - len(right_text.replace(self._groupChar, ''))
adjsel_to = sel_to + numrightgroups
## dbg('adjusting "previous" selection from', (sel_start, sel_to), 'to:', (adjsel_start, adjsel_to))
self._prevSelection = (adjsel_start, adjsel_to)
# determine appropriate selection for ungrouped undo
ignore, (new_sel_start, new_sel_to) = BaseMaskedTextCtrl._Undo(self, adjvalue, adjprev, just_return_results = True)
# adjust new selection based on grouping:
left_len = edit_end - new_sel_start
numleftgroups = left_len / 3
new_sel_start -= numleftgroups
if numleftgroups and left_len % 3 == 0:
new_sel_start += 1
if new_sel_start < self._masklength and prev[new_sel_start] == self._groupChar:
new_sel_start += 1
right_len = edit_end - new_sel_to
numrightgroups = right_len / 3
new_sel_to -= numrightgroups
if new_sel_to and prev[new_sel_to-1] == self._groupChar:
new_sel_to -= 1
if new_sel_start > new_sel_to:
new_sel_to = new_sel_start
# for numbers, we don't care about leading whitespace; adjust selection if
# it includes leading space.
prev_stripped = prev.lstrip()
prev_start = self._masklength - len(prev_stripped)
if new_sel_start < prev_start:
new_sel_start = prev_start
## dbg('adjusted selection accounting for grouping:', (new_sel_start, new_sel_to))
self._SetValue(prev)
self._SetInsertionPoint(new_sel_start)
self._SetSelection(new_sel_start, new_sel_to)
self._prevSelection = (new_sel_start, new_sel_to)
## dbg('resetting "prev selection" to', self._prevSelection)
## dbg(indent=0)
#=========================================================================== #===========================================================================
@@ -1606,6 +1730,10 @@ i=0
## 1. Add support for printf-style format specification. ## 1. Add support for printf-style format specification.
## 2. Add option for repositioning on 'illegal' insertion point. ## 2. Add option for repositioning on 'illegal' insertion point.
## ##
## Version 1.2
## 1. Allowed select/replace digits.
## 2. Fixed undo to ignore grouping chars.
##
## Version 1.1 ## Version 1.1
## 1. Fixed .SetIntegerWidth() and .SetFractionWidth() functions. ## 1. Fixed .SetIntegerWidth() and .SetFractionWidth() functions.
## 2. Added autoSize parameter, to allow manual sizing of the control. ## 2. Added autoSize parameter, to allow manual sizing of the control.
@@ -1613,4 +1741,4 @@ i=0
## nonsensical parameter methods from the control, so it will work ## nonsensical parameter methods from the control, so it will work
## properly with Boa. ## properly with Boa.
## 4. Fixed allowNone bug found by user sameerc1@grandecom.net ## 4. Fixed allowNone bug found by user sameerc1@grandecom.net
##

View File

@@ -190,7 +190,7 @@ class BaseMaskedTextCtrl( wx.TextCtrl, MaskedEditMixin ):
# make SetValue behave the same as if you had typed the value in: # make SetValue behave the same as if you had typed the value in:
try: try:
value = 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:
self._isNeg = False # (clear current assumptions) self._isNeg = False # (clear current assumptions)
value = self._adjustFloat(value) value = self._adjustFloat(value)
@@ -206,16 +206,17 @@ class BaseMaskedTextCtrl( wx.TextCtrl, MaskedEditMixin ):
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) ## dbg('adjusted value: "%s"' % value)
value = 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)
else: else:
## dbg('exception thrown', indent=0) ## dbg('exception thrown', indent=0)
raise raise
self._SetValue(value) # note: to preserve similar capability, .SetValue() self._SetValue(value) # note: to preserve similar capability, .SetValue()
# does not change IsModified() # does not change IsModified()
#### dbg('queuing insertion after .SetValue', self._masklength) #### dbg('queuing insertion after .SetValue', replace_to)
wx.CallAfter(self._SetInsertionPoint, self._masklength) # set selection to last char replaced by paste
wx.CallAfter(self._SetSelection, self._masklength, self._masklength) wx.CallAfter(self._SetInsertionPoint, replace_to)
wx.CallAfter(self._SetSelection, replace_to, replace_to)
## dbg(indent=0) ## dbg(indent=0)