wxMaskedTextCtrl v0.0.5

Hi Will,

First, let me say, WOW. And THANKS. This is thoughtful and extremely
helpful, not to mention a huge morale boost. What can I say? You da man.

Your weekend-long missive took all morning to respond to, what with the
aha!'s and all the notes I was taking. Below, find questions/responses and a
couple larger topics.

Again, many thanks.

Q: Is it your intention to make "home" and "end" mean
"home/end" of the current field, or just the whole
control? (either would be good, as right now, you can't
select and then hit backspace or delete...)

In rev 0.0.6, I have home/end moving to the beginning / end of the
user-entered information. I think this is more intuitive and works better
than the way I was doing it. Does this seem 'right'?

just use event.Skip() for these keys [Home/End]

Nice patch, I'll give it a try. Thanks!

## 4. Mask characters [A,a,X,#] cannot appear in the format string
as literals. I haven't yet determined how difficult this would be,
but it seems to me that the logical means of doing this is to have
'\' be another special syntax character

The trouble comes at the point of __isMaskChar and __isTemplateChar,
determining whether a given character is part of the mask or template. Hmm,
I -was- building a dictionary of positions to help with this, maybe I should
go back to that.... the key is the character position, then whatever data
needed, such as the template character, allowed keys, etc. I could build
the dictionary while parsing out special characters such as \ and #{3}
notation.... very Pythonistic!

I'm curious about what you consider 'messing up' the entered number?

Harumph. The user can begin entering the a number, then click 'ahead' in the
mask to resume entry. This leaves a nasty space in the middle of the number
that was giving value.strip() headaches. I replaced that with
string.replace(value," ","") and it seems to work better. I *think* in rev
0.0.6 I have the most likely issues handled. <crosses fingers>

IMO, a masked edit control should have its mask fixed in space
... [fixed-width font]

Oooh, this is great. It also resolves a continuing challenge I am having
with auto-sizing... yesterday I discovered that entering "MM" in the state
field causes one of the M's to disappear and the control to become very
difficult to use! Obviously, there is no state MM but any other 2-char field
would have the same problem. A fixed-width font would solve this nicely.
Hmm, maybe it could be an option (or even the default) so that those who
prefer the wxSWISS style can have it regardless...

The delete key should work, but it doesn't

I'm having challenges with this. It would be easy enough to remove the
current insertionpoint character, add a space at the end, and "scroll"
everything left through the mask. Trouble is, what happens when a character
that was legal in its current place would be illegal in the new one? For
example, consider the mask AAA###. What happens when you press Del at
position 2? The numbers cannot be allowed to move into the character range.

I considered enabling the first described behavior if the mask is
homogenous. For example, AAAAAA or ######. Then, they could delete and
scroll the contents to their hearts desire. But I would then disable Del in
heterogenous masks. What do you think?

The "signed" integer demo allows you to type a minus, but once
entered, you can't erase it.

They way I am handling it now is like a calculator; press - again and the
sign changes. In 0.0.6, the '+' is also enabled to sign the control
positive.

Similarly, you can select the "space" at the beginning reserved for a '-'

Bug. I now have it on the list.

Email regex

Whew, thanks. I'm a fledgling regexer and I just -knew- that one I did for
the email would be problematic. This is a great boon.

There's also a problem with using
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
as the mask for email addresses; if you hit "end",

Should be resolved with rev 0.0.6.

However, even using this, I find that what I really want is to go
to the end of what *I've* typed,

I think I have this dialed in. You'll have to check and see in 0.0.6. The
code for selecting text and the other shortcut keys are really terrific.
Thanks again. :slight_smile:

Your program doesn't exit on "close" if the 2nd window is brought
up, but I can't figure out why not yet...

Argh, it was because I copied my first test class too liberally and it was
derived from wxPySimpleApp and executed a second MainLoop, when it should
have been (and now is) derived from wxFrame. Blah.

As a matter of style, your code should have more comments;

Good point, will do.

[isDecimal sample comment]... (Was this what you intended?)

Yes :slight_smile:

String.ascii_{upper,lower}case are new as of python2.2; ...
I recommend instead using string.letters[26:] and
string.letters[:26] for backward compatibility.

Great, will do. I never knew a Python before 2.2x and I guess it shows :(...

Names like __get_allowedChars should really be __getAllowedChars.

Noted, will do.

It would also be nice if the behavior of the up and down arrows,
backspace, delete, cut, etc. were configurable, ie. overridable
without requiring rewrite of the entire OnChar function. Perhaps if
you split these things into subroutines, and effectively made a
means of writing "OnBackspace()" or "OnUpArrow()", then it would be
easier to make derived controls.

Yes, great idea. Done.

You should create 2 files before submission, one for the library,
eg. "maskedEdit.py" and one for the wxPython demo.

Good, will do.

you return from the OnChar function in 2 different places without
1st calling _dbg(indent=0)

I noticed that :stuck_out_tongue: I'll be more liberal with the indent option :wink:

Why do WXK_UP and WXK_DOWN result in a return rather than
just "charOk = false" (and keep going) like all the other such keys?
A bug?

Ahh, yes. Antiquated. On the list. Thanks!

IMO, there should be an overview in the overview string for the
wxPython demo that explains the interface to your control,

Yes, I'm trying to improve the doc a little with each rev. I was holding the
'overview' till 1.0.0 (release version) since the interface is still
changing (improving!)... but at the same time keeping the technical docs
current.

Should I worry about a submission for the wxWindows help file? I'd be happy
to write through documentation if there were a place for it to go...

For the demo, I suggest a wxNotebook with your
various demos on the tabs of the notebook; then you can extend the
demo as much as you want by adding tabs as you run out of room...

You like the notebook better than buttons opening wxFrames? I thought about
the notebook but the demo's default size is so tiny...

By convention, 8-space tabs are rarely used; most python I've seen
uses 4 space indenting and no tabs.

I've been test-driving the Wing IDE and its indentation is driving me crazy.
It has tons of options and I know it's in there somewhere but damn if I can
find it. Out of curiousity, which IDE/editor are you using?

TOPIC: Character-level masking (time control, etc)

In order to use your control as the base class for such controls,
it would be necessary to disallow certain sequences and control what
happens on erase.

Here's the basic structure (as it is developing):

  -- OnChar --
    -- key action -- ie. valid keypress or function key?
    -- type action -- ie. date/int/decimal/char
    -- 'validation' -- ie. set in/valid status

Say the user enters a '9'. First, it is checked for validity within the
mask. Is it a "#" type? Yes, so go on to the type handler. Is the control a
date type? Yes, check to see if an in-place formatting needs to occur (e.g.
change '67' to '1967' if in the year position). Finally, check to see if the
value is 'valid', also by type. In this case, wxDateTime.Parse is called to
see if a valid date is found.

As I follow your suggestion, an additional key filter would occur based on
control type. So, is the '9' in the first position of the month, day, or
year? If so, disallow entry. Is this right?

Or, what do you think about using the current structure, and creating some
additional auto-formatting events? In this example, if the user presses '9'
as the first char in the month or day position, it is replaced with '09' and
insertion is moved to the next position. It would work the same for time.

So now, the user enters '3867' and the date autoformats their entry on the
fly to '03/08/1967'. This does leave the sticky question of what happens
when they enter '1' in the first position but want '01'. But, it does rule
out the illegal values. Another question: if this is a preferred behavior,
how best to structure the code for ease of subclassing?

Currently, I have no specific time-type behavior in the control; this seems
to me to already be handled nicely by wxTimeCtrl!

TOPIC: Mixin Class Suggestions

Great idea, sounds scary. I haven't done -any- mixed-parent classes yet
(still quite the foreign concept). I understand the idea but have tons of
questions about the implementation. I may hold off on this one, how about
version 2?

with great regard and respect,

Jeff Childers
jchilders_98@yahoo.com

PS - got the attachment, thanks again.

···

-----Original Message-----
From: Will Sadkin [mailto:wsadkin@nameconnector.com]
Sent: Monday, February 17, 2003 9:44 AM
To: 'wxPython-users@lists.wxwindows.org'
Subject: RE: [wxPython-users] wxMaskedTextCtrl v0.0.5

Jeff,

First, let me say I'm flattered re: the comments at the top of your
code; I wish I had had the time last year to write *this* control, as
it would have made the job of writing wxTimeCtrl a lot easier if I had.
But at the time, I was a novice at writing derived controls, and so I
stuck to the specific problem I needed a solution for.

2nd, I have been meaning to write to you ever since you first posted
re: this project, and I apologize for not getting to in sooner.
I would love to help you with this, and to that end...

With your indulgence, I have a few comments about your v0.0.5 code;
this is long and fairly detailed, so bear with me. (It's taken me a
while to process it all, so I hope these comments are still relevant;
I've tried to eliminate feedback that others have already given.)

## Version 0.0.5 Current Issues:
## ===================================
##
## 2. Cut/Copy/Paste not yet addressed.

IMO, "cut" should not be allowed in a fixed-position masked control;
However, I'd make the meaning of "cut" be "blank the current
selection, minus the mask, of course... Copy is just fine; I'd leave
that action to be the default for wxTextCtrl.

However, Paste is a bit tricky...
For wxIntCtrl, I validate the paste buffer contents to make sure they're
legal digits, and then construct the candidate new value from the paste,
and then validate; if it ain't legal, it doesn't validate and the bell
sounds... Perhaps you can do something along these lines in a more
generic fashion?

## 3. How can I detect shift-key down when home or end are pressed (to
select?)

Use event.ShiftDown(). (See the wxKeyEvents demo for how.)
Q: Is it your intention to make "home" and "end" mean "home/end" of the
current field, or just the whole control? (either would be good, as
right now, you can't select and then hit backspace or delete...)

Actually, I don't see why you can't just use event.Skip() for these keys,
that is:
                elif key == WXK_HOME:
                        _dbg('key == WXK_HOME')
                        event.Skip()
                        charOk = false
                        
                elif key == WXK_END:
                        _dbg('key == WXK_END')
                        event.Skip()
                        charOk = false

I tried this, and it worked well; in fact using shift-end, followed by
delete brings the cursor to the start of the deleted value with the mask
intact -- very nice!

## 4. Mask characters [A,a,X,#] cannot appear in the format string as
literals.

I haven't yet determined how difficult this would be, but it seems to
me that the logical means of doing this is to have '\' be another
special syntax character meaning "escape", as it does in regexps;
this would be consistent with "conventional" syntax, and would allow
\A \a \X \# to mean a "literal" (and therefore "mask") character.
(Of course, you have to allow \\ to mean "literal \.")

## 5. It should allow some way to specify repeating masks, e.g.
(#3)-(#3)-(#4) - this
## could be parsed by re and the standard format string could be built
(###-###-####).
## Therefore no additional logic would be needed, just the syntax and the
re parser.

I have some trouble with this idea; how to you distinguish '(#3)'
from '(#3)'? :wink:
That is, this creates ambiguous syntax, where one is a shortcut
for ### and the other means '(', a digit, followed by the mask
characters '3)'. However, if you implement my suggestion for 4 above,
then literal numbers could be more "special syntax;" ie. you could
then write: #3-#12-#4 to mean ###-############-####, and
(#\3) to mean (#3).

Alternatively, you could use the same sort of syntax as for regexps,
ie #{3} means ###, and then to use '{' in the mask, you would use
'\{'. This is better, I think, as you could then extend the notion
to include + and * as special mask specification characters too, so
you could specify 'X+' as "1 or more letters, punctuation or digits."
(This would come in handy for dealing with a bug described below...)

## 6. Numeric handler needs re-writing to include all current possible
options, and to
## avoid cases where the user can 'messup' the entered number.

I spent a great deal of time getting this right in wxIntCtrl, and
I didn't tackle floating point at all. (But feel free to lift
whatever you want from there!) However, wxIntCtrl seems to me to
be a bit different than a "masked edit" control, in that it's
supposed to allow arbitrary numbers, without any "masked" parts or
fixed lengths of digits. Does your control allow this flexibility?
Without yet reading the code, I'm curious about what you consider
'messing up' the entered number?

## Question: can we blank the current number if the user starts to type
over
## the current number?

## To-Do's:
## =============================
## 3. New numeric values handler to improve overall functionality in
numeric fields.
##
## 4. Position cursor at next empty -entry- location on mouse click, or
beginning if
## the control is a) empty or b) full
##
## 5. Validate from list, or from file with list of valid values. Popup
list simulates
## combobox. Example: City validates against list of cities, or zip vs
zip code list.

Perhaps 5 should be done with a control derived from wxComboBox, ie.
wxMaskedComboBox? If you write your the bulk of your code for
controlling and validating the text entry as a mixin (I think their
text entry event mechanisms are almost identical), then you could
create both a wxMaskedComboBox and a wxMaskedTextCtrl without
duplicating most of your code... Then wxMaskedTextCtrl and
wxMaskedComboBox would just be something of the form:

class wxMasked<ctrl-type>(wx<ctrl-type>, wxMaskedEditMixin):
        def __init__( self, parent, id=-1, value = '',
                      pos = wxDefaultPosition,
                      size = wxDefaultSize,
                      mask = 'XXXXXXXXXXXXX',
                      formatcodes = '',
                      fillChar = " ",
                      excludeChars = '',
                      includeChars = '',
                      revalid = '',
                      autoformat = '',
                      style = wxTE_PROCESS_TAB,
                      name = 'formattedtextctrl'):

                wx<ctrl-type>.__init__(self, parent, id, value='',
                                    pos=pos, size= self.__calcSize(size),
                                    style=style, name=name)

    self.SetupMaskedEdit(mask, formatcodes, fillchar,
             excludeChars, includeChars,
             revalid, autoformat)

where <ctrl-type> is either TextCtrl or ComboBox, and 90% of the rest of
the code lives in the mixin. Then, for wxMaskedComboBox, as a last step
to the validation, you could check the resulting value against the values
in the dropdown. (Just a thought.)

====================================================================
Functional comments:

1) IMO, a masked edit control should have its mask fixed in space on the
   control. I found the fact that the mask characters move around because
   of the proportionally spaced font to be very disconcerting; at first I
   thought it was inserting characters, rather than replacing spaces!
   (I never had to deal with this in wxTimeCtrl, because all of the
   characters were always filled in, but in hindsight, I would add
   something along the lines of:

        font = self.GetFont()
        fixedfont = wxFont(font.GetPointSize(), font.GetFamily(),
                           font.GetStyle(), font.GetWeight(),
         font.GetUnderlined(), 'Courier New')
        self.SetFont(fixedfont)

   to the constructor, to force a fixed width font for the control, thus
   eliminating all the distracting useless "motion" of the mask.
   (I actually plan to do this in a patch to wxTimeCtrl as well.)

2) The delete key should work, but it doesn't (also commented on on the
   mailing list.) Similarly, home/end selection doesn't work (see fix
   under "current issues" (3) above and modified fix under (6) below.)

3) The "signed" integer demo allows you to type a minus, but once entered,
   you can't erase it. Similarly, you can select the "space"
   at the beginning reserved for a '-', but once entered, you can't select
   it anymore. (This seems like a bug to me; the '-' seems to be treated
   as a mask character once typed, but I don't think it should be.)

4) What you can type is often seemingly inconsistently validated; for
instance,
   you can't type anything but A or P to change AM to PM, but the format
   mask for the date and time will happily allow 13/13/2003 35:99 PM...
   It would be nice if you could optionally require input to be valid to
   be entered. (See suggestion at end.)

5) There are several problems with your email regexp:
   a) you're using when you should be using (). As a result, your
current
      control allows "wsadkin@nn" and doesn't allow "wsadkin@aaa.com".
   b) It doesn't account for the .edu, .mil, .gov domains, or the country
      codes.
   c) It doesn't account for dotted usernames like "William.Sadkin@abc.com"
   d) It doesnt account for multipart domains, like support.microsoft.com
   e) I don't think email address components can start with numbers, so I
      think the regexp should be something like:

[a-zA-Z](\.\w+|\w*)@\w+.([a-zA-Z]\w*\.)*(com|org|net|edu|mil|gov|(co\.)?\w\w
) *$

   rather than

  \w+@\w+[\.\w+]*[com|org|net] *$
   (I don't guarantee this is either necessary or sufficient, but it does
    come closer.
    Actually, I think there's probably an RFC or something that describes
    the particulars on valid email addresses; I'll look into that later
    and see if I can come up with something that is both.)

6) There's also a problem with using
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
   as the mask for email addresses; if you hit "end", you end up at the
   far end of the "value", nowhere near the end of what you typed, with no
   cursor visible. (at least under MSW.)
   It seems to me that there ought to be a format character modifier
   for "arbitrary length" (see suggestion for "current issue (5) above.)
   The control could then know that the text control could "expand" in size
   in those portions of the control, and insert rather than replace
   characters. The mask could then be "X*" allowing the input to grow as
   necessary while still showing when there's a match to a legal value.
   If that's too hard, or even if that's done, you still might want to
   consider the following change:

            elif key == WXK_END:
                _dbg('key == WXK_END')
                end = len(self.GetValue().rstrip())
                if event.ShiftDown():
                    _dbg("shift-end; select to end of non-whitespace")
                    # for some reason that escapes me, the following is
necessary to get the
                    # selection to work:
                    wxCallAfter(self.SetInsertionPoint, pos)
                    wxCallAfter(self.SetSelection, pos, end)
                else:
                    pos = end
                    self.SetInsertionPoint(pos)
                    charOk = false
  
   However, even using this, I find that what I really want is to go
   to the end of what *I've* typed, not the end of the mask. To do this
   would involve some sort of more sophisicated accounting...
   What I'd *really* like is to have End => end of the current field's
   user input, and Ctrl-End => end of all user input. (and shift-end select
   to the end of the current field's input, and ctrl-shift-end select to
   the end of the control's input.)

7) Your program doesn't exit on "close" if the 2nd window is brought
   up, but I can't figure out why not yet...

====================================================================
Code comments:

1) As a matter of style, your code should have more comments;
   specifically, every function should have a triple-quoted doc
   string that indicates what it does, what it returns, and
   what (if any) subtleties it has. This is consistent with the
   coding guidelines, and makes it easier to remember a year from
   now what exactly you were thinking... :slight_smile: Eg:

   def isDecimal( fmtstring ):
       """
       This function returns true if the the format string allows
       of the form of a POSITIVE floating point number.
       """
   (Was this what you intended?)

2) string.ascii_{upper,lower}case are new as of python2.2; this will
   make your control unusable for anyone using an earlier versions of
   python. I recommend instead using string.letters[26:] and
   string.letters[:26] for backward compatibility.

3) You should create 2 files before submission, one for the library,
   eg. "maskedEdit.py" and one for the wxPython demo. (eg. timectrl.py
   for the lib, and wxTimeCtrl.py for the demo.) If you go with the mixin
   idea, then I'd create 3, maskedEditMixin.py maskededit.py and
   wxMaskedEdit.py. For the demo, I suggest a wxNotebook with your
   various demos on the tabs of the notebook; then you can extend the
   demo as much as you want by adding tabs as you run out of room...

4) IMO, there should be an overview in the overview string for the
   wxPython demo that explains the interface to your control, particularly
   the tricky bits. (Eg. the format codes seem a little obscure...)

5) Names like __get_allowedChars should really be __getAllowedChars.

6) There are some bugs in your use of the _dbg() function; you
   return from the OnChar function in 2 different places without 1st
   calling _dbg(indent=0), resulting in ever increasing indents;
   unfortunately the _dbg() function I wrote doesn't know when you
   change scope, and so you have to manage the indentation
   level before every exit from a function if you're using indenting.

7) Why do WXK_UP and WXK_DOWN result in a return rather than
   just "charOk = false" (and keep going) like all the other such keys?
   A bug?

8) By convention, 8-space tabs are rarely used; most python I've seen
   uses 4 space indenting and no tabs. (So to be consistent
   with the rest of the wxPython source, I'd change to this prior to
   submission.) Also, Robin prefers no trailing whitespace on any line;
   I wrote a little python filter I use to ensure this is the case on
   any code I submit, and I've included it with this message for you.

========================================================
Suggested functionality:

I wrote wxTimeCtrl and, recently, wxIntCtrl to be controls that take and
return different types than strings; because of this, I needed the control
to prevent entry of a value that couldn't be converted to the appropriate
type. So, wxTimeCtrl doesn't allow erasure, because it would result
in an illegal time. Similarly, wxIntCtrl allows erasure so long as the
string can still be converted to an integer, or if it allows None as a
value; otherwise, it "does something reasonable", ie. replaces the value
with 0.

In order to use your control as the base class for such controls,
it would be necessary to disallow certain sequences and control what
happens on erase.

It would also need better accounting/validation of "fields" within the
masked
control would help with these things. For instance, for wxTimeCtrl, I
have the option of either military or 12hr time; if military, then an
hour value of 00 -23 is allowed, but not 30; if 12 hour then the 1st digit
can only be 0 if the 2nd is 1-9, and 1 iff the 2nd digit is 0, 1 or 2.
So, if the value is currently 03, and I move the cursor to before the
0, the control needs to disallow any entry to replace the 0, because
no value would be legal. (This might be achievable simply 'seeding'
the control with a legal value and then using the regexp match to
allow/prevent inputs rather than doing only coloring.)

It would also be nice if the behavior of the up and down arrows,
backspace, delete, cut, etc. were configurable, ie. overridable
without requiring rewrite of the entire OnChar function. Perhaps if
you split these things into subroutines, and effectively made a means
of writing "OnBackspace()" or "OnUpArrow()", then it would be easier to
make derived controls.

=========================================================

Whew!
That took all weekend to write... I haven't looked at all of it
in detail, but I figure I better post this before it reaches novel
length, and is all obsolete!

Regards,
/Will Sadkin
Parlance Corporation

---------------------------------------------------------------------
To unsubscribe, e-mail: wxPython-users-unsubscribe@lists.wxwindows.org
For additional commands, e-mail: wxPython-users-help@lists.wxwindows.org

Jeff Childers wrote:

Should I worry about a submission for the wxWindows help file? I'd be happy
to write through documentation if there were a place for it to go...

No. wxPython.lib docs are going to be handled differently in the future. Just be sure to have good doc strings for the public classes and methods. We'll eventually have tools that take it the rest of the way.

···

--
Robin Dunn
Software Craftsman
http://wxPython.org Java give you jitters? Relax with wxPython!

Jeff:

RE:

I'm having challenges with this. It would be easy enough to remove the current
insertionpoint character, add a space at the end, and "scroll" everything left
through the mask...

What I'm used to simply deletes the selected char and doesn't move the rest of
the string at all. I think this behavior would be appropriate for a date, phone
number, ip address or similar mask. This avoids the problems of how to scroll
heterogeneous masks. I wouldn't disable delete, since IMO that's a sure recipe
for helpdesk calls!

HTH,

John