X-Git-Url: https://git.saurik.com/wxWidgets.git/blobdiff_plain/ebc89b9f61e89f9da580f62e2d79179a3db38654..9fd1fa4b1a6a25fe70e703ceb68231c771de5d4e:/wxPython/wx/lib/masked/maskededit.py diff --git a/wxPython/wx/lib/masked/maskededit.py b/wxPython/wx/lib/masked/maskededit.py index 3bde5a3ca5..c64c48588e 100644 --- a/wxPython/wx/lib/masked/maskededit.py +++ b/wxPython/wx/lib/masked/maskededit.py @@ -757,7 +757,7 @@ import wx from wx.tools.dbg import Logger dbg = Logger() -##dbg(enable=0) +##dbg(enable=1) ## ---------- ---------- ---------- ---------- ---------- ---------- ---------- @@ -1914,10 +1914,12 @@ class MaskedEditMixin: self._prevValue = newvalue # disallow undo of sign type if self._autofit: -## dbg('setting client size to:', self._CalcSize()) - size = self._CalcSize() - self.SetSizeHints(size) - self.SetClientSize(size) +## dbg('calculated size:', self._CalcSize()) + self.SetClientSize(self._CalcSize()) + width = self.GetSize().width + height = self.GetBestSize().height +## dbg('setting client size to:', (width, height)) + self.SetBestFittingSize((width, height)) # Set value/type-specific formatting self._applyFormatting() @@ -1992,9 +1994,23 @@ class MaskedEditMixin: self._SetInitialValue() if self._autofit: - size = self._CalcSize() - self.SetSizeHints(size) - self.SetClientSize(size) + # this is tricky, because, as Robin explains: + # "Basically there are two sizes to deal with, that are potentially + # different. The client size is the inside size and may, depending + # on platform, exclude the borders and such. The normal size is + # the outside size that does include the borders. What you are + # calculating (in _CalcSize) is the client size, but the sizers + # deal with the full size and so that is the minimum size that + # we need to set with SetBestFittingSize. The root of the problem is + # that in _calcSize the current client size height is returned, + # instead of a height based on the current font. So I suggest using + # _calcSize to just get the width, and then use GetBestSize to + # get the height." + self.SetClientSize(self._CalcSize()) + width = self.GetSize().width + height = self.GetBestSize().height + self.SetBestFittingSize((width, height)) + # Set value/type-specific formatting self._applyFormatting() @@ -2644,7 +2660,7 @@ class MaskedEditMixin: sizing_text += 'M' #### dbg('len(sizing_text):', len(sizing_text), 'sizing_text: "%s"' % sizing_text) w, h = self.GetTextExtent(sizing_text) - size = (w+4, self.GetClientSize().height) + size = (w+4, self.GetSize().height) #### dbg('size:', size, indent=0) return size @@ -2692,7 +2708,7 @@ class MaskedEditMixin: ## dbg('ignoring bogus text change event', indent=0) pass else: -## dbg('curvalue: "%s", newvalue: "%s"' % (self._curValue, newvalue)) +## dbg('curvalue: "%s", newvalue: "%s", len(newvalue): %d' % (self._curValue, newvalue, len(newvalue))) if self._Change(): if self._signOk and self._isNeg and newvalue.find('-') == -1 and newvalue.find('(') == -1: ## dbg('clearing self._isNeg') @@ -2866,6 +2882,8 @@ class MaskedEditMixin: if newfield != field and newfield._selectOnFieldEntry: ## dbg('queuing selection: (%d, %d)' % (newfield._extent[0], newfield._extent[1])) wx.CallAfter(self._SetSelection, newfield._extent[0], newfield._extent[1]) + else: + wx.CallAfter(self._SetSelection, newpos, new_select_to) keep_processing = False elif keep_processing: @@ -3091,7 +3109,7 @@ class MaskedEditMixin: def _OnCtrl_A(self,event=None): """ Handles ctrl-a keypress in control. Should return False to skip other processing. """ end = self._goEnd(getPosOnly=True) - if not event or event.ShiftDown(): + if not event or (isinstance(event, wx.KeyEvent) and event.ShiftDown()): wx.CallAfter(self._SetInsertionPoint, 0) wx.CallAfter(self._SetSelection, 0, self._masklength) else: @@ -3100,7 +3118,7 @@ class MaskedEditMixin: 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.""" ## dbg("MaskedEditMixin::_OnErase", indent=1) sel_start, sel_to = self._GetSelection() ## check for a range of selected text @@ -3318,6 +3336,11 @@ class MaskedEditMixin: ## dbg(indent=0) return False + if just_return_value: +## dbg(indent=0) + return newstr + + # else... ## dbg('setting value (later) to', newstr) wx.CallAfter(self._SetValue, newstr) ## dbg('setting insertion point (later) to', pos) @@ -3397,12 +3420,12 @@ class MaskedEditMixin: def _OnReturn(self, event): """ - Changes the event to look like a tab event, so we can then call - event.Skip() on it, and have the parent form "do the right thing." + Swallows the return, issues a Navigate event instead, since + masked controls are "single line" by defn. """ ## dbg('MaskedEditMixin::OnReturn') - event.m_keyCode = wx.WXK_TAB - event.Skip() + self.Navigate(True) + return False def _OnHome(self,event): @@ -3483,9 +3506,6 @@ class MaskedEditMixin: """ Primarily handles TAB events, but can be used for any key that designer wants to change fields within a masked edit control. - NOTE: at the moment, although coded to handle shift-TAB and - control-shift-TAB, these events are not sent to the controls - by the framework. """ ## dbg('MaskedEditMixin::_OnChangeField', indent = 1) # determine end of current field: @@ -3497,7 +3517,10 @@ class MaskedEditMixin: self._AdjustField(pos) if event.GetKeyCode() == wx.WXK_TAB: ## dbg('tab to next ctrl') - event.Skip() + # As of 2.5.2, you don't call event.Skip() to do + # this, but instead force explicit navigation, if + # wx.TE_PROCESS_TAB is used (like in the masked edits) + self.Navigate(True) #else: do nothing ## dbg(indent=0) return False @@ -3531,7 +3554,10 @@ class MaskedEditMixin: self._AdjustField(pos) if event.GetKeyCode() == wx.WXK_TAB: ## dbg('tab to previous ctrl') - event.Skip() + # As of 2.5.2, you don't call event.Skip() to do + # this, but instead force explicit navigation, if + # wx.TE_PROCESS_TAB is used (like in the masked edits) + self.Navigate(False) else: ## dbg('position at beginning') wx.CallAfter(self._SetInsertionPoint, field_start) @@ -3577,7 +3603,10 @@ class MaskedEditMixin: self._AdjustField(pos) if event.GetKeyCode() == wx.WXK_TAB: ## dbg('tab to next ctrl') - event.Skip() + # As of 2.5.2, you don't call event.Skip() to do + # this, but instead force explicit navigation, if + # wx.TE_PROCESS_TAB is used (like in the masked edits) + self.Navigate(True) else: ## dbg('position at end') wx.CallAfter(self._SetInsertionPoint, field_end) @@ -3591,7 +3620,10 @@ class MaskedEditMixin: self._AdjustField(pos) if event.GetKeyCode() == wx.WXK_TAB: ## dbg('tab to next ctrl') - event.Skip() + # As of 2.5.2, you don't call event.Skip() to do + # this, but instead force explicit navigation, if + # wx.TE_PROCESS_TAB is used (like in the masked edits) + self.Navigate(True) #else: do nothing ## dbg(indent=0) return False @@ -3637,6 +3669,8 @@ class MaskedEditMixin: if fraction._selectOnFieldEntry: ## dbg('queuing selection after decimal point to:', (start, end)) wx.CallAfter(self._SetSelection, start, end) + else: + wx.CallAfter(self._SetSelection, start, start) keep_processing = False if self._isInt: ## handle integer value, truncate from current position @@ -3651,6 +3685,7 @@ class MaskedEditMixin: if newstr.find(')') != -1: newpos -= 1 # (don't move past right paren) wx.CallAfter(self._SetInsertionPoint, newpos) + wx.CallAfter(self._SetSelection, newpos, newpos) keep_processing = False ## dbg(indent=0) @@ -3946,6 +3981,7 @@ class MaskedEditMixin: pos = pos+2 if newvalue != value: +## dbg('old value: "%s"\nnew value: "%s"' % (value, newvalue)) self._SetValue(newvalue) self._SetInsertionPoint(pos) @@ -4039,6 +4075,8 @@ class MaskedEditMixin: self._SetInsertionPoint(pos) if pos < sel_to: # restore selection self._SetSelection(pos, sel_to) + else: + self._SetSelection(pos, pos) ## dbg('adjusted pos:', pos, indent=0) return pos @@ -4712,11 +4750,28 @@ class MaskedEditMixin: newtext = "" 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: # adjustpos must have moved the position; make selection match: sel_start = sel_to = pos ## 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 and ((sel_start, sel_to) == field._extent # and whole field selected or (sel_start == sel_to # or nothing selected @@ -4872,7 +4927,8 @@ class MaskedEditMixin: if match_index is not None and partial_match: matched_str = newtext 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(newtext.rstrip())) match_field = self._ctrl_constraints if self._ctrl_constraints._insertRight: # adjust position to just after partial match in control: @@ -4898,6 +4954,8 @@ class MaskedEditMixin: we need to pull the following trick: """ ## dbg('MaskedEditMixin::_OnFocus') + if self.IsBeingDeleted() or self.GetParent().IsBeingDeleted(): + return wx.CallAfter(self._fixSelection) event.Skip() self.Refresh() @@ -5161,7 +5219,10 @@ class MaskedEditMixin: the control, and deselect. """ ## dbg('MaskedEditMixin::_fixSelection', indent=1) - if not self._mask or not self._IsEditable(): + # can get here if called with wx.CallAfter after underlying + # control has been destroyed on close, but after focus + # events + if not self or not self._mask or not self._IsEditable(): ## dbg(indent=0) return @@ -5425,11 +5486,31 @@ class MaskedEditMixin: field = self._FindField(sel_start) edit_start, edit_end = field._extent 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 - 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) - 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 # non-empty value is selected, and there's room if the selection is expanded leftward: @@ -5492,7 +5573,7 @@ class MaskedEditMixin: if not wx.Validator_IsSilent(): wx.Bell() ## dbg(indent=0) - return False + return None, -1 # else... text = self._eraseSelection() @@ -5523,17 +5604,19 @@ class MaskedEditMixin: wx.CallAfter(self._SetInsertionPoint, new_pos) else: ## dbg(indent=0) - return new_text + return new_text, replace_to elif just_return_value: ## dbg(indent=0) - return self._GetValue() + return self._GetValue(), sel_to ## dbg(indent=0) - def _Undo(self): + def _Undo(self, value=None, prev=None, just_return_results=False): """ Provides an Undo() method in base controls. """ ## dbg("MaskedEditMixin::_Undo", indent=1) - value = self._GetValue() - prev = self._prevValue + if value is None: + value = self._GetValue() + if prev is None: + prev = self._prevValue ## dbg('current value: "%s"' % value) ## dbg('previous value: "%s"' % prev) if prev is None: @@ -5590,6 +5673,7 @@ class MaskedEditMixin: for next_op in range(len(code_5tuples)-1, -1, -1): 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]) + field = self._FindField(i2) if op == 'insert' and prev[j1:j2] != self._template[j1:j2]: ## dbg('insert found: selection =>', (j1, j2)) sel_start = j1 @@ -5597,9 +5681,8 @@ class MaskedEditMixin: diff_found = True break elif op == 'delete' and value[i1:i2] != self._template[i1:i2]: - field = self._FindField(i2) 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_to = i2 else: @@ -5609,23 +5692,39 @@ class MaskedEditMixin: diff_found = True break elif op == 'replace': -## dbg('replace found: selection =>', (j1, j2)) - sel_start = j1 - sel_to = j2 + if not prev[i1:i2].strip() and field._insertRight: + sel_start = sel_to = j2 + else: + sel_start = j1 + sel_to = j2 +## dbg('replace found: selection =>', (sel_start, sel_to)) diff_found = True break if diff_found: # now go forwards, looking for earlier changes: +## dbg('searching forward...') for next_op in range(len(code_5tuples)): op, i1, i2, j1, j2 = code_5tuples[next_op] field = self._FindField(i1) if op == 'equal': continue elif op == 'replace': -## dbg('setting sel_start to', i1) - sel_start = i1 + if field._insertRight: + # 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 elif op == 'insert' and not value[i1:i2]: ## dbg('forward %s found' % op) @@ -5637,9 +5736,21 @@ class MaskedEditMixin: ## dbg('setting sel_start to inserted space:', j1) sel_start = j1 break - elif op == 'delete' and field._insertRight and not value[i1:i2].lstrip(): - continue + elif op == 'delete': +## 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: +## dbg('unknown code!') # we've got what we need break @@ -5693,15 +5804,22 @@ class MaskedEditMixin: prev_sel_start, prev_sel_to = self._prevSelection field = self._FindField(sel_start) - - if self._signOk and (self._prevValue[sel_start] in ('-', '(', ')') - or self._curValue[sel_start] in ('-', '(', ')')): + if( self._signOk + and sel_start < self._masklength + and (prev[sel_start] in ('-', '(', ')') + or value[sel_start] in ('-', '(', ')')) ): # 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 - elif field._groupdigits and (self._curValue[sel_start:sel_to] == field._groupChar - or self._prevValue[sel_start:sel_to] == field._groupChar): + elif field._groupdigits and (value[sel_start:sel_to] == field._groupChar + or prev[sel_start:sel_to] == field._groupChar): # 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 else: @@ -5713,7 +5831,13 @@ class MaskedEditMixin: if prev_select_len >= calc_select_len: # 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 and prev_sel_to < len(self._template) # and prev_sel_to not at end of control @@ -5744,27 +5868,44 @@ class MaskedEditMixin: test_sel_start, test_sel_to = prev_sel_start, prev_sel_to ## dbg('test selection:', (test_sel_start, test_sel_to)) -## dbg('calc change: "%s"' % self._prevValue[sel_start:sel_to]) -## dbg('test change: "%s"' % self._prevValue[test_sel_start:test_sel_to]) +## dbg('calc change: "%s"' % prev[sel_start:sel_to]) +## dbg('test change: "%s"' % prev[test_sel_start:test_sel_to]) # if calculated selection spans characters, and same characters # "before" the previous insertion point are present there as well, # select the ones related to the last known selection instead. if( sel_start != sel_to 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 + # 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('previous value: "%s"' % self._prevValue) - self._SetValue(self._prevValue) +## dbg('previous value: "%s"' % prev) +## dbg(indent=0) + if just_return_results: + return prev, (sel_start, sel_to) + # else... + self._SetValue(prev) self._SetInsertionPoint(sel_start) self._SetSelection(sel_start, sel_to) + else: ## 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): @@ -5792,8 +5933,8 @@ class MaskedEditMixin: wx.EVT_MENU(menu, wx.ID_SELECTALL, self._OnCtrl_A) # ## WSS: The base control apparently handles - # enable/disable of wID_CUT, wxID_COPY, wxID_PASTE - # and wxID_CLEAR menu items even if the menu is one + # enable/disable of wx.ID_CUT, wx.ID_COPY, wx.ID_PASTE + # and wx.ID_CLEAR menu items even if the menu is one # we created. However, it doesn't do undo properly, # so we're keeping track of previous values ourselves. # Therefore, we have to override the default update for @@ -6285,6 +6426,19 @@ i=1 ## 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. +## 5. Fixed tabbing to work with 2.5.2 semantics. +## 6. Fixed size calculation to handle changing fonts +## ## Version 1.6 ## 1. Reorganized masked controls into separate package, renamed things accordingly ## 2. Split actual controls out of this file into their own files.