+                                        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)