]> git.saurik.com Git - wxWidgets.git/blobdiff - wxPython/wx/lib/masked/textctrl.py
Added new MaskedEditControl code from Will Sadkin. The modules are
[wxWidgets.git] / wxPython / wx / lib / masked / textctrl.py
diff --git a/wxPython/wx/lib/masked/textctrl.py b/wxPython/wx/lib/masked/textctrl.py
new file mode 100644 (file)
index 0000000..d73fbe4
--- /dev/null
@@ -0,0 +1,325 @@
+#----------------------------------------------------------------------------
+# Name:         masked.textctrl.py
+# Authors:      Jeff Childers, Will Sadkin
+# Email:        jchilders_98@yahoo.com, wsadkin@nameconnector.com
+# Created:      02/11/2003
+# Copyright:    (c) 2003 by Jeff Childers, Will Sadkin, 2003
+# Portions:     (c) 2002 by Will Sadkin, 2002-2003
+# RCS-ID:       $Id$
+# License:      wxWidgets license
+#----------------------------------------------------------------------------
+#
+# This file contains the most typically used generic masked control,
+# masked.TextCtrl.  It also defines the BaseMaskedTextCtrl, which can
+# be used to derive other "semantics-specific" classes, like masked.NumCtrl,
+# masked.TimeCtrl, and masked.IpAddrCtrl.
+#
+#----------------------------------------------------------------------------
+import  wx
+from wx.lib.masked import *
+
+# jmg 12/9/03 - when we cut ties with Py 2.2 and earlier, this would
+# be a good place to implement the 2.3 logger class
+from wx.tools.dbg import Logger
+dbg = Logger()
+##dbg(enable=0)
+
+# ## TRICKY BIT: to avoid a ton of boiler-plate, and to
+# ## automate the getter/setter generation for each valid
+# ## control parameter so we never forget to add the
+# ## functions when adding parameters, this loop
+# ## programmatically adds them to the class:
+# ## (This makes it easier for Designers like Boa to
+# ## deal with masked controls.)
+#
+# ## To further complicate matters, this is done with an
+# ## extra level of inheritance, so that "general" classes like
+# ## MaskedTextCtrl can have all possible attributes,
+# ## while derived classes, like TimeCtrl and MaskedNumCtrl
+# ## can prevent exposure of those optional attributes of their base
+# ## class that do not make sense for their derivation.  Therefore,
+# ## we define
+# ##    BaseMaskedTextCtrl(TextCtrl, MaskedEditMixin)
+# ## and
+# ##    MaskedTextCtrl(BaseMaskedTextCtrl, MaskedEditAccessorsMixin).
+# ##
+# ## This allows us to then derive:
+# ##    MaskedNumCtrl( BaseMaskedTextCtrl )
+# ##
+# ## and not have to expose all the same accessor functions for the
+# ## derived control when they don't all make sense for it.
+# ##
+
+class BaseMaskedTextCtrl( wx.TextCtrl, MaskedEditMixin ):
+    """
+    This is the primary derivation from MaskedEditMixin.  It provides
+    a general masked text control that can be configured with different
+    masks.  It's actually a "base masked textCtrl", so that the
+    MaskedTextCtrl class can be derived from it, and add those
+    accessor functions to it that are appropriate to the general class,
+    whilst other classes can derive from BaseMaskedTextCtrl, and
+    only define those accessor functions that are appropriate for
+    those derivations.
+    """
+
+    def __init__( self, parent, id=-1, value = '',
+                  pos = wx.DefaultPosition,
+                  size = wx.DefaultSize,
+                  style = wx.TE_PROCESS_TAB,
+                  validator=wx.DefaultValidator,     ## placeholder provided for data-transfer logic
+                  name = 'maskedTextCtrl',
+                  setupEventHandling = True,        ## setup event handling by default
+                  **kwargs):
+
+        wx.TextCtrl.__init__(self, parent, id, value='',
+                            pos=pos, size = size,
+                            style=style, validator=validator,
+                            name=name)
+
+        self.controlInitialized = True
+        MaskedEditMixin.__init__( self, name, **kwargs )
+
+        self._SetInitialValue(value)
+
+        if setupEventHandling:
+            ## Setup event handlers
+            self.Bind(wx.EVT_SET_FOCUS, self._OnFocus )         ## defeat automatic full selection
+            self.Bind(wx.EVT_KILL_FOCUS, self._OnKillFocus )    ## run internal validator
+            self.Bind(wx.EVT_LEFT_DCLICK, self._OnDoubleClick)  ## select field under cursor on dclick
+            self.Bind(wx.EVT_RIGHT_UP, self._OnContextMenu )    ## bring up an appropriate context menu
+            self.Bind(wx.EVT_KEY_DOWN, self._OnKeyDown )        ## capture control events not normally seen, eg ctrl-tab.
+            self.Bind(wx.EVT_CHAR, self._OnChar )               ## handle each keypress
+            self.Bind(wx.EVT_TEXT, self._OnTextChange )         ## color control appropriately & keep
+                                                                ## track of previous value for undo
+
+
+    def __repr__(self):
+        return "<BaseMaskedTextCtrl: %s>" % self.GetValue()
+
+
+    def _GetSelection(self):
+        """
+        Allow mixin to get the text selection of this control.
+        REQUIRED by any class derived from MaskedEditMixin.
+        """
+        return self.GetSelection()
+
+    def _SetSelection(self, sel_start, sel_to):
+        """
+        Allow mixin to set the text selection of this control.
+        REQUIRED by any class derived from MaskedEditMixin.
+        """
+####        dbg("MaskedTextCtrl::_SetSelection(%(sel_start)d, %(sel_to)d)" % locals())
+        return self.SetSelection( sel_start, sel_to )
+
+    def SetSelection(self, sel_start, sel_to):
+        """
+        This is just for debugging...
+        """
+##        dbg("MaskedTextCtrl::SetSelection(%(sel_start)d, %(sel_to)d)" % locals())
+        wx.TextCtrl.SetSelection(self, sel_start, sel_to)
+
+
+    def _GetInsertionPoint(self):
+        return self.GetInsertionPoint()
+
+    def _SetInsertionPoint(self, pos):
+####        dbg("MaskedTextCtrl::_SetInsertionPoint(%(pos)d)" % locals())
+        self.SetInsertionPoint(pos)
+
+    def SetInsertionPoint(self, pos):
+        """
+        This is just for debugging...
+        """
+##        dbg("MaskedTextCtrl::SetInsertionPoint(%(pos)d)" % locals())
+        wx.TextCtrl.SetInsertionPoint(self, pos)
+
+
+    def _GetValue(self):
+        """
+        Allow mixin to get the raw value of the control with this function.
+        REQUIRED by any class derived from MaskedEditMixin.
+        """
+        return self.GetValue()
+
+    def _SetValue(self, value):
+        """
+        Allow mixin to set the raw value of the control with this function.
+        REQUIRED by any class derived from MaskedEditMixin.
+        """
+##        dbg('MaskedTextCtrl::_SetValue("%(value)s")' % locals(), indent=1)
+        # Record current selection and insertion point, for undo
+        self._prevSelection = self._GetSelection()
+        self._prevInsertionPoint = self._GetInsertionPoint()
+        wx.TextCtrl.SetValue(self, value)
+##        dbg(indent=0)
+
+    def SetValue(self, value):
+        """
+        This function redefines the externally accessible .SetValue to be
+        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
+        from the base wx control.
+        """
+##        dbg('MaskedTextCtrl::SetValue = "%s"' % value, indent=1)
+
+        if not self._mask:
+            wx.TextCtrl.SetValue(self, value)    # revert to base control behavior
+            return
+
+        # empty previous contents, replacing entire value:
+        self._SetInsertionPoint(0)
+        self._SetSelection(0, self._masklength)
+        if self._signOk and self._useParens:
+            signpos = value.find('-')
+            if signpos != -1:
+                value = value[:signpos] + '(' + value[signpos+1:].strip() + ')'
+            elif value.find(')') == -1 and len(value) < self._masklength:
+                value += ' '    # add place holder for reserved space for right paren
+
+        if( len(value) < self._masklength                # value shorter than control
+            and (self._isFloat or self._isInt)            # and it's a numeric control
+            and self._ctrl_constraints._alignRight ):   # and it's a right-aligned control
+
+##            dbg('len(value)', len(value), ' < self._masklength', self._masklength)
+            # try to intelligently "pad out" the value to the right size:
+            value = self._template[0:self._masklength - len(value)] + value
+            if self._isFloat and value.find('.') == -1:
+                value = value[1:]
+##            dbg('padded value = "%s"' % value)
+
+        # make SetValue behave the same as if you had typed the value in:
+        try:
+            value = self._Paste(value, raise_on_invalid=True, just_return_value=True)
+            if self._isFloat:
+                self._isNeg = False     # (clear current assumptions)
+                value = self._adjustFloat(value)
+            elif self._isInt:
+                self._isNeg = False     # (clear current assumptions)
+                value = self._adjustInt(value)
+            elif self._isDate and not self.IsValid(value) and self._4digityear:
+                value = self._adjustDate(value, fixcentury=True)
+        except ValueError:
+            # If date, year might be 2 digits vs. 4; try adjusting it:
+            if self._isDate and self._4digityear:
+                dateparts = value.split(' ')
+                dateparts[0] = self._adjustDate(dateparts[0], fixcentury=True)
+                value = string.join(dateparts, ' ')
+##                dbg('adjusted value: "%s"' % value)
+                value = self._Paste(value, raise_on_invalid=True, just_return_value=True)
+            else:
+##                dbg('exception thrown', indent=0)
+                raise
+
+        self._SetValue(value)   # note: to preserve similar capability, .SetValue()
+                                # does not change IsModified()
+####        dbg('queuing insertion after .SetValue', self._masklength)
+        wx.CallAfter(self._SetInsertionPoint, self._masklength)
+        wx.CallAfter(self._SetSelection, self._masklength, self._masklength)
+##        dbg(indent=0)
+
+
+    def Clear(self):
+        """ Blanks the current control value by replacing it with the default value."""
+##        dbg("MaskedTextCtrl::Clear - value reset to default value (template)")
+        if self._mask:
+            self.ClearValue()
+        else:
+            wx.TextCtrl.Clear(self)    # else revert to base control behavior
+
+
+    def _Refresh(self):
+        """
+        Allow mixin to refresh the base control with this function.
+        REQUIRED by any class derived from MaskedEditMixin.
+        """
+##        dbg('MaskedTextCtrl::_Refresh', indent=1)
+        wx.TextCtrl.Refresh(self)
+##        dbg(indent=0)
+
+
+    def Refresh(self):
+        """
+        This function redefines the externally accessible .Refresh() to
+        validate the contents of the masked control as it refreshes.
+        NOTE: this must be done in the class derived from the base wx control.
+        """
+##        dbg('MaskedTextCtrl::Refresh', indent=1)
+        self._CheckValid()
+        self._Refresh()
+##        dbg(indent=0)
+
+
+    def _IsEditable(self):
+        """
+        Allow mixin to determine if the base control is editable with this function.
+        REQUIRED by any class derived from MaskedEditMixin.
+        """
+        return wx.TextCtrl.IsEditable(self)
+
+
+    def Cut(self):
+        """
+        This function redefines the externally accessible .Cut to be
+        a smart "erase" of the text in question, so as not to corrupt the
+        masked control.  NOTE: this must be done in the class derived
+        from the base wx control.
+        """
+        if self._mask:
+            self._Cut()             # call the mixin's Cut method
+        else:
+            wx.TextCtrl.Cut(self)    # else revert to base control behavior
+
+
+    def Paste(self):
+        """
+        This function redefines the externally accessible .Paste to be
+        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
+        from the base wx control.
+        """
+        if self._mask:
+            self._Paste()                   # call the mixin's Paste method
+        else:
+            wx.TextCtrl.Paste(self, value)   # else revert to base control behavior
+
+
+    def Undo(self):
+        """
+        This function defines the undo operation for the control. (The default
+        undo is 1-deep.)
+        """
+        if self._mask:
+            self._Undo()
+        else:
+            wx.TextCtrl.Undo(self)   # else revert to base control behavior
+
+
+    def IsModified(self):
+        """
+        This function overrides the raw wxTextCtrl method, because the
+        masked edit mixin uses SetValue to change the value, which doesn't
+        modify the state of this attribute.  So, we keep track on each
+        keystroke to see if the value changes, and if so, it's been
+        modified.
+        """
+        return wx.TextCtrl.IsModified(self) or self.modified
+
+
+    def _CalcSize(self, size=None):
+        """
+        Calculate automatic size if allowed; use base mixin function.
+        """
+        return self._calcSize(size)
+
+
+class TextCtrl( BaseMaskedTextCtrl, MaskedEditAccessorsMixin ):
+    """
+    This extra level of inheritance allows us to add the generic set of
+    masked edit parameters only to this class while allowing other
+    classes to derive from the "base" masked text control, and provide
+    a smaller set of valid accessor functions.
+    """
+    pass
+
+