Keyboard Listener Question

214 views
Skip to first unread message

Dan Casper

unread,
May 2, 2007, 9:56:39 AM5/2/07
to Google Web Toolkit
I'm working on implementing a keyboard listener that'll prevent non-
numeric entries to several text boxes, but I'm running into some
trouble. The approach I am using closely parallels the example out of
the docs:

static KeyboardListener numOnly = new KeyboardListener() {
public void onKeyDown(Widget sender, char keyCode, int modifiers) {

}
public void onKeyPress(Widget sender, char keyCode, int modifiers) {
if (Character.isDigit(keyCode)) {
} else if (keyCode == 46 &&
((TextBox)sender).getText().lastIndexOf(".") == -1) {
} else {
((TextBox)sender).cancelKey();
}
}
public void onKeyUp(Widget sender, char keyCode, int modifiers) {

}

};

The trouble I am having is that this prevents the user from using the
backspace key, the tab key, or any other non-numeric input key for
that matter. Is there a good way to implement a keyboard listener
that only allows numeric input and still allows the other non-alpha
keys to be pressed? Thanks,

--Dan

Ian Petersen

unread,
May 2, 2007, 10:41:51 AM5/2/07
to Google-We...@googlegroups.com
On 5/2/07, Dan Casper <diggi...@hotmail.com> wrote:
> The trouble I am having is that this prevents the user from using the
> backspace key, the tab key, or any other non-numeric input key for
> that matter. Is there a good way to implement a keyboard listener
> that only allows numeric input and still allows the other non-alpha
> keys to be pressed? Thanks,

The short answer is "no" because there's no good way. The longer
answer is "there is an ugly way".

Here's the problem for Firefox and IE (I don't know much about Safari
and Opera):

- Firefox (and all Gecko browsers) permit you to filter control keys
(backspace, delete, etc.) _and_ "regular" keys (alphanumerics,
punctuation, etc.) from the onKeyPress event because the event object
that is passed to onKeyPress listeners defines both a keyCode and a
charCode. In the case of a control key, charCode is zero and keyCode
is non-zero. In the case of "regular" keys, charCode is non-zero (I
forget if keyCode is zero in this case).

- IE confuses things in onKeyPress because the keyCode field is
overloaded to represent both control keys and regular keys (so things
like the F-keys conflict with some punctuation, I think). So, in IE,
you need to listen to onKeyDown as well because, at least in IE,
onKeyDown gives only keyboard scan codes in its event object so each
key is uniquely identifiable. This is a pain, though because
lowercase and uppercase characters are distinguishable only by the
state of the shift key (I don't know what happens when CapsLock is
on).

The solution I once used in a native Javascript app was to hook
onKeyDown with an IE-specific listener and onKeyPress with both an
IE-specific listener _and_ a Firefox-specific listener. Every
listener would detach itself if it discovered it was running in the
wrong browser. The Firefox-specific listener did things the easy way:
check charCode and keyCode and do the right thing. The IE-specific
listeners worked in tandem. The onKeyDown listener would check to see
if the user had pressed a control key and, if so, signal the upcoming
onKeyPress listener via a Javascript flag to do nothing. The
onKeyPress listener would handle regular keys so long as the
aforementioned flag was not set.

This hideous hack generally worked, and, if I remember right, it even
worked in Safari the first time we tried it. I've never tried it in
Opera. I don't know if it'll work in GWT, because I'm not sure what
information GWT passes in to keyboard listeners. You may have to use
onBrowserEvent and do some magic with the DOM class or maybe even
JSNI.

Good luck,
Ian

--
Tired of pop-ups, security holes, and spyware?
Try Firefox: http://www.getfirefox.com

Mark Volkmann

unread,
May 2, 2007, 10:53:59 AM5/2/07
to Google-We...@googlegroups.com
Renier suggested something like the following which works for me.

addKeyboardListener(new KeyboardListenerAdapter() {


public void onKeyPress(Widget sender, char keyCode, int
modifiers) {

if (!(keyCode <= 31 || validKey(keyCode))) {
((TextBox) sender).cancelKey();
}
}
});

...

public boolean validKey(char keyCode) {
return allowedChars.indexOf(keyCode) != -1;

Reinier Zwitserloot

unread,
May 2, 2007, 10:59:13 AM5/2/07
to Google Web Toolkit
Actually, for him, there's a specific easy fix:

just check if the keyCode received is below 32. If so, let it pass. To
be complete, also let pass 127. (in US-ASCII, control characters are
defined as anything between 0 and 31 inclusive, and 127).

for example, tab is 9, carriage return is 10, line feed is 13,
backspace is 8, delete is 127 (sometimes), etcetera.

In IE that check will do nothing interesting, but in all other
browsers it will do what you want: Allow control characters to work as
normal.

On May 2, 4:41 pm, "Ian Petersen" <ispet...@gmail.com> wrote:

Ian Petersen

unread,
May 2, 2007, 11:41:09 AM5/2/07
to Google-We...@googlegroups.com
The problem with Mark and Reinier's solutions is the navigation keys.
I created the following EntryPoint as a test app:

public void onModuleLoad() {
RootPanel.get().add(new Foo());
}

private static final class Foo extends VerticalPanel implements
KeyboardListener {

private Label lastLabel;

public Foo() {
TextBox textBox = new TextBox();
add(textBox);

textBox.addKeyboardListener(this);
}

public void onKeyDown(Widget sender, char keyCode, int modifiers) {

lastLabel = new Label("keydown code: " + (int) keyCode + ",
char: '" + keyCode + "'", false);
insert(lastLabel, 1);
}

public void onKeyPress(Widget sender, char keyCode, int modifiers) {

String oldText = lastLabel.getText();

lastLabel.setText(oldText + " keypress code: " + (int) keyCode
+ ", char: '" + keyCode + "'");

lastLabel = null;
}

public void onKeyUp(Widget sender, char keyCode, int modifiers) {
}
}

When I run it in hosted mode on Linux (which uses Mozilla--I don't
have a Windows machine here to test IE), I find the following results:

Home key:
keydown code: 36, char: '$' keypress code: 36, char: '$'
$ key (Shift + 4):
keydown code: 52, char: '4' keypress code: 36, char: '$'
End key:
keydown code: 35, char: '#' keypress code: 35, char: '#'
# key (Shift + 3):
keydown code: 51, char: '3' keypress code: 35, char: '#'
Left Arrow key (not on the number pad):
keydown code: 37, char: '%' keypress code: 37, char: '%'
% key (Shift + 5):
keydown code: 53, char: '5' keypress code: 37, char: '%'
F1 key:
keydown code: 112, char: 'p' keypress code: 112, char: 'p'
p key:
keydown code: 80, char: 'P' keypress code: 112, char: 'p'

And so on. So, I don't think the user experience will be very good if
you limit your key filtering to the solutions suggested by Mark and
Reinier--either the user will not be able to move the cursor around or
the user will be able to put characters like $, %, and # into the
field because onKeyPress can't distinguish these keys. If you want to
create a different sort of filtering text box that permits only alpha
characters, for example, then you run into trouble with the function
keys.

Sandy McArthur

unread,
May 2, 2007, 12:07:18 PM5/2/07
to Google-We...@googlegroups.com
On 5/2/07, Dan Casper <diggi...@hotmail.com> wrote:
> I'm working on implementing a keyboard listener that'll prevent non-
> numeric entries to several text boxes, but I'm running into some
> trouble. The approach I am using closely parallels the example out of
> the docs:
>
> static KeyboardListener numOnly = new KeyboardListener() {
[snipped]

Even if you get a KeyboardListener that does what you want don't rely
on it completely. Embedded browsers such as Opera on mobile phones or
the Nintendo Wii don't fire keyboard events because when a user
activates a input box it brings up an on screen virtual input type
interface. On change events seem to work so you may want to put back
up logic there.

--
Sandy McArthur

"He who dares not offend cannot be honest."
- Thomas Paine

Ian Petersen

unread,
May 2, 2007, 12:30:23 PM5/2/07
to Google-We...@googlegroups.com
For what it's worth, I've just run the compiled version of my little
test app in IE 6 (via Wine), Opera 9.20, and Firefox 2.0.0.3, all on
x86 Linux. It seems Firefox and Opera do the same things. The
biggest difference between Firefox and IE is that control keys
generate a keydown but not a keypress. Of course, I don't know how
accurate my IE results are since I'm running it through Wine, but I've
done this before, and the results seem familiar.

I think the "perfect" GWT key filter needs to listen to keydown and
keypress. The keydown listener will have to check the keyCode it's
given against a list of known control keys and, if the current key is
a control key, set a flag and optionally pass it to a filtration
method. The keypress listener then needs to do nothing if the
aforementioned flag is set or process the key as a non-control key
otherwise.

Of course, as Sandy pointed out, no key filter can be truly perfect.
He mentions unusual browsers like mobile Opera, but you also have to
remember that the user can paste text into a field without generating
any key events, and, depending on how inclusive you want to be, you'll
need to test with accessibility tools for people with limited control
of their fingers, or no fingers at all (voice-command and on-screen
keyboards). Since you're building a web-app and English isn't the
only language on the 'net, you may be concerned about other things,
too. What happens if one of your users is in China? I'm pretty sure
the keyboards over there are similar to mine (or maybe the same) but
the operating system usually provides a way to use a few keystrokes to
enter Chinese characters. I have no clue how that looks to a key
listener (ie. do you get one event corresponding to the final
character or an event for each keystroke?).

Mark Volkmann

unread,
May 2, 2007, 12:45:01 PM5/2/07
to Google-We...@googlegroups.com

Are you expecting to create a web app. that correctly handles Home,
End, function keys, and so on? I didn't expect that those would do
anything useful in a web app. The only navigation key I assume will
work is the tab key and that works fine if I call setTabIndex on my
widgets. I limit the characters that can be entered in my TextBox
widgets. If $ is an allowed character, I guess it's not a big problem
if the user is able to enter that by pressing the Home key. If $
isn't an allowed character then I prevent it from being entered. To
this point I've only tested in hosted mode on a Mac, in Safari and in
Firefox. All of those seem to handle my basic character filtering
correctly.

Ian Petersen

unread,
May 2, 2007, 1:16:52 PM5/2/07
to Google-We...@googlegroups.com
On 5/2/07, Mark Volkmann <ma...@ociweb.com> wrote:
> Are you expecting to create a web app. that correctly handles Home,
> End, function keys, and so on? I didn't expect that those would do
> anything useful in a web app.

Well, if you're trying to create a textbox that only permits numbers
(as in Dan's original query), then I'd expect the Home key to put the
cursor at the beginning of the text field. If you filter out keys in
onKeyPress with an expression like this:

if (keyCode < 32 || keyCode == 127) {
// permit control keys
}
else if ((keyCode >= '0' && keyCode <= '9') || keyCode == '.' ||
keyCode == '-') {
// permit "numeric" keys
}
else {
// block everything else
}

then, at least in Firefox and Opera on Linux, and probably also on
Windows, the Home key will break because its keyCode is 36 and that
falls into the 'else' case. The above code will work in IE because
the Home key doesn't generate an onKeyPress event in IE so the filter
never gets run, but (as far as I can tell) every key except Alt,
Control, and Shift generate both onKeyDown and onKeyPress in Firefox
and Opera so the filter will run and it will exclude Home, End, and
all the arrows. If you decide to restrict input to integers and so
you don't make an exception for the period character, then you'll also
block the delete key because it's a complete accident that delete and
period generate the same keyCode in Firefox's onKeyPress.

Regarding handling function keys, I think (although I haven't tested
this, so I could be wrong), blocking all but the digits using the
above code will also prevent the user from pressing F5 to reload the
current page because F5 collides with lowercase t. This could be a
happy accident since reloading a web-app can be a problem, but it is
an accident nonetheless.

Abdullah Jibaly

unread,
May 2, 2007, 2:04:59 PM5/2/07
to Google-We...@googlegroups.com
Quick question, if I just want numbers in a textfield, is the best
approach to add an onChange listener and call something like isNumber?

Thanks,
Abdullah

Mark Volkmann

unread,
May 2, 2007, 2:11:48 PM5/2/07
to Google-We...@googlegroups.com
On May 2, 2007, at 1:04 PM, Abdullah Jibaly wrote:

> Quick question, if I just want numbers in a textfield, is the best
> approach to add an onChange listener and call something like isNumber?

I've written a RegExpTextBox class that allows me to specify allowed
characters and a regular expression to validate against when focus
leaves the field. It uses JSNI to evaluate the regular expression
using JavaScript. I'd be happy to share that code if anybody wants
it. It does have the issues that Ian Petersen has been pointing out
though (handling special keys).

Dan Casper

unread,
May 2, 2007, 4:37:30 PM5/2/07
to Google Web Toolkit
Wow, I didn't realize I was going to stir up an antfarm on this one,
but that's OK, I like lots of input. Specifically what I'm attempting
to accomplish is a "Generic" keyboard listener I can use to attach to
things like Phone Number Fields, Fax Numbers, Quantities, Costs, Etc
(anything where you really only want numbers to be acceptable). I'll
see if I can't take some of the suggestions from here and get it to do
what I need it to.

Also, to Mark Volkmann: I'd very much like to see the code you've got
for a RegExp TextBox that allows for regular expression testing (I
might try to adapt it so it determines if an email address is valid or
something to that effect). Thanks all for the input.

--Dan

On May 2, 12:11 pm, Mark Volkmann <m...@ociweb.com> wrote:
> On May 2, 2007, at 1:04 PM, Abdullah Jibaly wrote:
>
> > Quick question, if I just want numbers in a textfield, is the best
> > approach to add an onChange listener and call something like isNumber?
>
> I've written a RegExpTextBox class that allows me to specify allowed
> characters and a regular expression to validate against when focus
> leaves the field. It uses JSNI to evaluate the regular expression
> using JavaScript. I'd be happy to share that code if anybody wants
> it. It does have the issues that Ian Petersen has been pointing out
> though (handling special keys).
>
>
>
> > Thanks,
> > Abdullah
>

> > On 5/2/07, Ian Petersen <ispet...@gmail.com> wrote:

Reinier Zwitserloot

unread,
May 3, 2007, 8:50:35 AM5/3/07
to Google Web Toolkit
After playing around with this stuff a little myself, the conclusion
is a bit disappointing:

You basically can't do this. That level of fine grained control of
textboxes isn't available.

However, on the flip-side, you do get plenty of opportunity. While you
can't be certain that every key struck whilst in a textbox will pass
by your code in a completely identifiable manner, MOST of the time you
do.

Thus, any solution must be holistic; it must look at the entirety of
the textfield, not just the last character, and decide if what's there
so far meshes with what's expected.

The ways to set this up are relatively simple: Just send onKeyUp,
onChange, onFocusLost, and if you need it (to catch right-click/paste,
for example), a timer that triggers every 100 milliseconds or so, and
redirect them ALL to the same method.

This method will then use stuff mentioned in this thread already:
check each character, or just match on regular expression.

This brings us to phase #2: Given that the input is 'broken', what do
you do?

One rather harsh tactic is to just truncate or clean up the input.
Taking out characters from a string which is supposed to contain just
numbers might still be acceptable, but, on the whole: If the user has
been entering illegal characters, eliminating them is unlikely to
produce the right result - what's probably going on is that the user
has misunderstood what he's supposed to type there.

Here's another scenario: textboxes with a limit on how many characters
you enter. As a rule webforms tend to eliminate characters typed in
excess. That's wrong from a user interface point of view: If I'm
typing beyond the limit, then I'll need to eliminate some characters,
but it's unlikely that I want to eliminate the last sentence.

So, what I usually do: I turn the textbox's background red, and I pop
up a little balloon that explains why the current contents of this
textbox aren't acceptable. I don't change the text at all, I just
aggressively draw or remove that balloon (onChange, onFocus,
onFocusLost, etc). If the user tries to hit 'submit' or whatever
passes for "I'm done with this input, please accept it and let me move
on to the next step", I shake the balloon to make abundantly clear
they need to read what it says.

A demo of this effect can be seen here:

http://zwitserloot.com/tipit-gwtlib/example

though I did notice the last version I uploaded is sort of broken in
MSIE6. Don't know how that got past my conformance tests. I'll address
it soon.

Ian Petersen

unread,
May 3, 2007, 3:28:34 PM5/3/07
to Google-We...@googlegroups.com
On 5/3/07, Reinier Zwitserloot <rein...@gmail.com> wrote:
>
> After playing around with this stuff a little myself, the conclusion
> is a bit disappointing:
>
> You basically can't do this. That level of fine grained control of
> textboxes isn't available.
>
> However, on the flip-side, you do get plenty of opportunity. While you
> can't be certain that every key struck whilst in a textbox will pass
> by your code in a completely identifiable manner, MOST of the time you
> do.

I think Reinier might be right. However, in the interest of
identifying keys most of the time, have a look at the code I'm
including at the bottom.

Basically I've created a class called KeyFilter that implements
KeyboardListener but classifies keys as either "control" keys or
"regular" keys and passes each key off to either onControlKey or
onRegularKey, as appropriate. If either method returns false, the key
is blocked by calling an abstract method called cancelKey. There's a
subclass of KeyFilter called TextBoxKeyFilter that implements
cancelKey by calling ((TextBox) sender).cancelKey(). I couldn't
figure out how to cancel the "current" event in a generic fashion--it
seems you need to be in the onBrowserEvent method to do that--so users
of KeyFilter on any widget besides TextBox will have to implement
cancelKey themselves.

I've tested the code in Firefox 2.0.0.3, Opera 9.20 and Internet
Explorer 6, all on x86 Linux (IE was tested via Wine). It generally
works. (I've included my test entry point at the bottom, too, so you
can run it yourself.) Here are the caveats that I've discovered:

1. Not all keys can be blocked. For example, pressing F4 always opens
some kind of sidebar in IE, even if the code tries to block the key

2. Opera reports different keyCodes for 1 and ! in onKeyDown (the
other browsers report the same code for both keys in onKeyDown). This
means that, for example, ! collides with Page Down so the
implementation below can't distinguish between some of the shifted
numbers (1, 3, 4, 5, 7, and 9, I think) and some control keys.
Currently the shifted numbers get reported as control keys rather than
the other way around.

3. I can't test in Safari, or on Windows or Mac. In particular, I
don't know if a Mac's keyboard has control keys that don't exist on a
non-Mac keyboard.

4. A user who I presume wishes to remain anonymous sent me an off-list
email saying that using the SCIM input method in SuSE Linux lets him
input Chinese characters into his browser. The result was a single
keystroke event when the character was finished. I think this means
that, on non-US English keyboards, certain "extended" characters can
be entered directly, and IE reports the Windows key as char 225, which
is an accented lowercase a, so I don't know what happens if you happen
to be typing something in French. (Probably a collision between that
a and the windows key.)

5. My code assumes that an onKeyPress is always preceded by a related
onKeyDown. If this assumption is ever broken then the key being
processed will cause undefined behaviour.

I don't think 1 can be solved. Anybody on the list who's interested
can solve 3 by running the test programme and reporting back. 4 may
or may not be a problem but someone with a keyboard that can directly
enter á (lower case a with acute accent) could tell us if IE
distinguishes between that character and the windows key. I don't
think 5 is a problem.

I think 2 can be solved, but it requires a different approach. I
created a quick test page in plain HTML and Javascript. The only
difference between Shift + 1 and Shift + Page Up in Opera is that the
onKeyPress event object reports event.which == 0 in the case of Page
Up and event.which == 33 in the case of !. GWT makes it impossible to
distinguish these cases without resorting to JSNI because the DOM
class returns something like

(event.which || event.keyCode)

in the relevant method.

So, in summary, if you're happy with the limitations I've outlined
above, you can use the following code to classify keys as either
control keys or regular keys. For my own purposes, I'm going to
investigate how to generate different code for Opera so I can solve
problem 2. If I ever get it working, I'll post the results here.

Ian

Code follows:

// ----- KeyFilter ------

import com.google.gwt.user.client.ui.KeyboardListener;
import com.google.gwt.user.client.ui.Widget;

public abstract class KeyFilter implements KeyboardListener {

public static final char KEY_ALT = 18;
public static final char KEY_ARROW_DOWN = 40;
public static final char KEY_ARROW_LEFT = 37;
public static final char KEY_ARROW_RIGHT = 39;
public static final char KEY_ARROW_UP = 38;
public static final char KEY_BACK_SPACE = 8;
public static final char KEY_CAPS_LOCK = 20; // this key is 0 in
Opera/Linux
public static final char KEY_CTRL = 17;
public static final char KEY_DELETE = 46;
public static final char KEY_END = 35;
public static final char KEY_ESC = 27;
public static final char KEY_F1 = 112;
public static final char KEY_F10 = 121;
public static final char KEY_F11 = 122;
public static final char KEY_F12 = 123;
public static final char KEY_F2 = 113;
public static final char KEY_F3 = 114;
public static final char KEY_F4 = 115;
public static final char KEY_F5 = 116;
public static final char KEY_F6 = 117;
public static final char KEY_F7 = 118;
public static final char KEY_F8 = 119;
public static final char KEY_F9 = 120;
public static final char KEY_HOME = 36;
public static final char KEY_INSERT = 45;
public static final char KEY_MENU = 93; // this key can't
be trapped in Opera/Linux
public static final char KEY_NUMLOCK = 144;
public static final char KEY_PAGE_DOWN = 34;
public static final char KEY_PAGE_UP = 33;
public static final char KEY_PAUSE_BREAK = 19;
public static final char KEY_RETURN = 13;
public static final char KEY_SHIFT = 16;
public static final char KEY_TAB = 9;

// this key is 0 in Firefox/Linux
// this key can't be trapped in Opera/Linux
// the value 225 was derived from IE6/Wine
public static final char KEY_WINDOWS = 225;

private static boolean isControlKey(char keyCode) {
return (keyCode <= KEY_ARROW_DOWN && keyCode != 32) // 32 is
the spacebar
|| (keyCode == KEY_INSERT)
|| (keyCode == KEY_DELETE)
|| (keyCode == KEY_MENU)
|| (KEY_F1 <= keyCode && keyCode <= KEY_F12)
|| (keyCode == KEY_NUMLOCK)
|| (keyCode == KEY_WINDOWS);
}

private boolean skipKeyPress;

public final void onKeyDown(Widget sender, char keyCode, int modifiers) {
if (isControlKey(keyCode)) {
if (!onControlKey(sender, keyCode, modifiers)) {
cancelKey(sender);
}

skipKeyPress = true;
}
else {
skipKeyPress = false;
}
}

public final void onKeyPress(Widget sender, char keyCode, int modifiers) {
if (skipKeyPress) {
return;
}

if (!onRegularKey(sender, keyCode, modifiers)) {
cancelKey(sender);
}
}

public final void onKeyUp(Widget sender, char keyCode, int modifiers) {
}

/**
* Cancel's the current keyboard event on <code>sender</code>.
*
* @param sender the widget on which a keyboard event is currently active
*/
protected abstract void cancelKey(Widget sender);

/**
* Called when a control key is pressed in an observed widget.
Note that some control
* keys cannot be blocked in some browsers or on some platforms.
*
* @param sender the widget that generated the keypress
* @param keyCode the keycode for the key that was pressed
* @param modifiers the modifier keys that were depressed when the
event occurred.
* This value is a combination of the bits defined by
* <code>{@link KeyboardListener#MODIFIER_SHIFT}</code>,
* <code>{@link KeyboardListener#MODIFIER_CTRL}</code>, and
* <code>{@link KeyboardListener#MODIFIER_ALT}</code>.
* @return <code>true</code> to let the key pass or
<code>false</code> to block the
* key
*/
protected abstract boolean onControlKey(Widget sender, char
keyCode, int modifiers);

/**
* Called when a non-control key is pressed in an observed widget.
*
* @param sender the widget that generated the keypress
* @param keyCode the Unicode character generated by the key that
was pressed
* @param modifiers the modifier keys that were depressed when the
event occurred.
* This value is a combination of the bits defined by
* <code>{@link KeyboardListener#MODIFIER_SHIFT}</code>,
* <code>{@link KeyboardListener#MODIFIER_CTRL}</code>, and
* <code>{@link KeyboardListener#MODIFIER_ALT}</code>.
* @return <code>true</code> to let the key pass or
<code>false</code> to block the
* key
*/
protected abstract boolean onRegularKey(Widget sender, char
keyCode, int modifiers);
}

// ----- TextBoxKeyFilter ------

import com.google.gwt.user.client.ui.TextBox;
import com.google.gwt.user.client.ui.Widget;

public abstract class TextBoxKeyFilter extends KeyFilter {

protected final void cancelKey(Widget sender) {
((TextBox) sender).cancelKey();
}
}

// ----- Example entry point ------
// to test the filter code, run the following entry point, put the cursor
// in the text box that appears, and hit some keys to see if they're
// properly classified

import com.google.gwt.core.client.EntryPoint;
import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.RootPanel;
import com.google.gwt.user.client.ui.TextBox;
import com.google.gwt.user.client.ui.VerticalPanel;
import com.google.gwt.user.client.ui.Widget;

public final class ExampleEntryPoint implements EntryPoint {

public void onModuleLoad() {
RootPanel.get().add(new Foo());
}

private static final class Foo extends VerticalPanel {

public Foo() {
TextBox textBox = new TextBox();

textBox.addKeyboardListener(new TextBoxKeyFilter() {

protected boolean onControlKey(Widget sender, char
keyCode, int modifiers) {
insert(new Label("Control Key: " + (int) keyCode), 1);
return false; // try to block the key
}

protected boolean onRegularKey(Widget sender, char
keyCode, int modifiers) {
insert(new Label("Regular Key: '" + keyCode + "'
(" + (int) keyCode + ")"), 1);
return false; // try to block the key
}
});

add(textBox);

Reinier Zwitserloot

unread,
May 3, 2007, 8:02:32 PM5/3/07
to Google Web Toolkit
That's some fine work. I'd test it on safari but I just handed my
macbook to apple and awaiting for its replacement.

On May 3, 9:28 pm, "Ian Petersen" <ispet...@gmail.com> wrote:

> import ...
>
> read more »

Ian Petersen

unread,
May 7, 2007, 3:57:54 PM5/7/07
to Google-We...@googlegroups.com
On 5/3/07, Reinier Zwitserloot <rein...@gmail.com> wrote:
> After playing around with this stuff a little myself, the conclusion
> is a bit disappointing:
>
> You basically can't do this. That level of fine grained control of
> textboxes isn't available.

So I've played around with this some more, and Reinier is even more
right than I originally thought.

You may want to read this guy's blog post:
http://my.opera.com/hallvors/blog/show.dml/217592

Basically the summary is that keyboard events were never standardized
by the W3C, and every browser does it differently. Furthermore, the
results are _greatly_ impacted by the keyboard you're using. For
example (from the above blog):

> Also, be aware that some locales use [AltGr]
> to type characters, and from JavaScript this will
> set event.ctrlKey so if you detect the S key and
> the [Ctrl] key for your editor's "save" command
> you're wrong and all Poles will hate you unless
> they use a browser that hacks around that issue.
> This is a problem on important[1], major[2] sites.

[1] GMail
[2] Blogger

I changed my keyboard settings a little so that I could do things like this:

1. Produce the Euro sign (€ ) by pressing Right Alt + 5
2. Produce accented characters like á by pressing Ctrl + ', a or ä by
pressing Ctrl + Shift + ", a

The results were baffling.

In Firefox, Right Alt + 5 produces keydown, keypress, keypress, keyup.
The first keypress has charCode == keyCode == 0, the second keypress
has charCode == 8364, which is the Unicode codepoint for the Euro
symbol. On the other hand, Ctrl + ', a produces no key events at all.
That's right! None!

In Opera, Right Alt + 5 produces a normal key sequence, but composed
keys, like á produce a keypress without a keydown or keyup. Opera
seemed most likely to work well because it only needs the keypress
event to distinguish between control keys and regular keys, but it has
problems with Home, End, Insert, and Delete, which are
indistinguishable from $, #, -, and ., respectively. Fortunately, I
think it's also impossible to block these keys in Opera, so, if you
wanted to, you could block $ without blocking Home.

IE 6 running under Wine actually worked the best. (Being a bit of a
Firefox zealot, and having struggled to make IE do anything useful,
this floored me.) My key filter correctly identifies all the
keystrokes I can think of within IE because its behaviour is actually
fairly consistent. The rules in IE seem to be:

- control keys generate keydown and keyup but not keypress
- everything else generates keydown, keypress, keyup
- in keydown and keyup, event.keyCode is some kind of keyboard scan
code for all keys
- in keypress, event.keyCode is the Unicode code point for all
characters that fit into Unicode and it's the same value as for
keydown otherwise

Unfortunately, I don't have either a Mac or Windows machine here, so I
can't test Safari until Friday, and I can't test Firefox or Opera on
anything but Linux.

So, to summarize, keyboard support in GWT's supported browsers really sucks.

For those that are interested, I did manage to refactor my KeyFilter
class to have different implementations in IE, Mozilla, and Opera. By
separating the implementations out by user agent, Opera can now
successfully distinguish between some keys that it couldn't
distinguish with the code in my previous post. As I mentioned,
though, I can't test in Safari until Friday, so my new code just
throws an exception in that browser. Once I get Safari tested (and
hopefully working) I'll see about posting the code here.

Ian Petersen

unread,
May 15, 2007, 4:06:33 PM5/15/07
to Google-We...@googlegroups.com
I don't know if anyone's still interested in this thread, but I
managed to do some research with Safari last Friday (May 11, 2007) and
I was pleasantly surprised. Safari 2.something seems to implement
some subset of the DOM Level 3 Events Specification
(http://www.w3.org/TR/2003/NOTE-DOM-Level-3-Events-20031107/). The
important bit is that key events have a keyIdentifier attribute
(http://www.w3.org/TR/2003/NOTE-DOM-Level-3-Events-20031107/events.html#Events-KeyboardEvent-keyIdentifier),
which makes it trivial to discriminate between control keys and
regular keys in the onKeyPress event.

In a Safari keypress event, Regular keys have keyIdentifier =
'U+ddddd' where ddddd is the Unicode number for the key expressed in
hex, and control keys have keyIdentifier = 'name' where name is
something like 'F1', or 'Enter'. The one hitch is that some keys that
I consider control keys, like Tab, have a keyIdentifier that looks
like a regular key, but these special cases all have Unicode numbers
less than 0x20, so it's still trivial to check for them.

The result of my research is that Safari can correctly filter nearly
every key. The remaining problem is (if I remember right--I didn't
take notes) that the Mac automatically permits composed keys and they
aren't properly recognized. For example, the key combination 'Option
+ N, N' produces ñ, but it might not be possible to trap that key
stroke. (I'm not sure about this--I'll test again this coming Friday
and report back.) Other special keys can be filtered, though. For
example, 'Option + A' produces ä and this _can_ be filtered.

While I was in an office with some Macs, I decided to test Firefox,
too. Unfortunately, my existing implementation breaks on Firefox/Mac.
The broken key that sticks in my memory is !. Shift + 1 produces a
keydown event with keyCode == 0 and then a keypress event with the
right keyCode. My existing implementation determines in onKeyDown
that a keyCode of zero must be a control key so it skips the keypress
event and the ! gets passed to onControlKey. Today I have fixed the
implementation of the Firefox key filter but I can't test it on a Mac
until Friday. Assuming everything works, I'll post all the code then.

Reinier Zwitserloot

unread,
May 15, 2007, 6:53:22 PM5/15/07
to Google Web Toolkit
Once you get this stuff on the rails and once GWT 1.4 is released, I
vote we petition this for inclusion in GWT itself. The frequency of
questions about keyboard shenanigans in here is at about one a week,
so clearly it's something many users run into. The size of the
translation table could be problematic, so it should probably not be
something in the 'standard' keylistener - instead some sort of
keylistener wrapper that you can use at will. That way if you don't
use it, the GWT compiler won't include any of it in the output JS and
size is not much of an issue. Provided you're okay with that of
course.


On May 15, 10:06 pm, "Ian Petersen" <ispet...@gmail.com> wrote:
> I don't know if anyone's still interested in this thread, but I
> managed to do some research with Safari last Friday (May 11, 2007) and
> I was pleasantly surprised. Safari 2.something seems to implement
> some subset of the DOM Level 3 Events Specification
> (http://www.w3.org/TR/2003/NOTE-DOM-Level-3-Events-20031107/). The
> important bit is that key events have a keyIdentifier attribute

> (http://www.w3.org/TR/2003/NOTE-DOM-Level-3-Events-20031107/events.htm...),

Ian Petersen

unread,
May 15, 2007, 7:55:33 PM5/15/07
to Google-We...@googlegroups.com
On 5/15/07, Reinier Zwitserloot <rein...@gmail.com> wrote:
> Once you get this stuff on the rails and once GWT 1.4 is released, I
> vote we petition this for inclusion in GWT itself. The frequency of
> questions about keyboard shenanigans in here is at about one a week,
> so clearly it's something many users run into. The size of the
> translation table could be problematic, so it should probably not be
> something in the 'standard' keylistener - instead some sort of
> keylistener wrapper that you can use at will. That way if you don't
> use it, the GWT compiler won't include any of it in the output JS and
> size is not much of an issue.

I think this is a good idea, but I'd argue the "translation table"
isn't really that big. There are only about 6 constants in use in the
code that I intend to post (I think I've already posted that bit--it's
in isControlKey()). So, unless someone chose to use the public
constants to check for a particular key, I think the compiler would
eliminate the unused ones anyway.

As for other potential size problems, the solution I've found ends up
requiring a separate implementation for each user agent, so I think
each browser-specific implementation is pretty lean.

Personally, I'd vote for a change to KeyboardListener to look
something like this:

public interface KeyboardListener {

// some keyCode constants here

void onKeyDown(Widget sender, char keyCode, int modifiers);

/** return false to preventDefault() */
boolean onKeyPress(Widget sender, char keyCode, int modifiers);

/** return false to preventDefault() */
boolean onControlKeyPress(Widget sender, char keyCode, int modifiers);

void onKeyUp(Widget sender, char keyCode, int modifiers);
}

I suppose this might need to be tweaked a bit for naming, or there
might be some design issues I'm not considering, but I like the
general shape of the above.

> Provided you're okay with that of course.

Yeah, I'm fine with that. I still have to fax in a CLA, or whatever
it's called (giving Google permission to include my code), but I
suppose I should be getting around to that anyway.

Reinier Zwitserloot

unread,
May 16, 2007, 12:51:41 AM5/16/07
to Google Web Toolkit
I just attached mine to a patch. A fax? I vaguely recall a story from
my great great grandpappy about them. Some sort of primitive pre-
internet version of email or something.

On May 16, 1:55 am, "Ian Petersen" <ispet...@gmail.com> wrote:

Ian Petersen

unread,
May 16, 2007, 1:47:13 AM5/16/07
to Google-We...@googlegroups.com
On 5/16/07, Reinier Zwitserloot <rein...@gmail.com> wrote:
> I just attached mine to a patch. A fax? I vaguely recall a story from
> my great great grandpappy about them. Some sort of primitive pre-
> internet version of email or something.

Huh. According to this:
http://code.google.com/legal/individual-cla-v1.0.html you have to
either snail-mail it or fax it. I suppose this is the wrong place to
ask for clarification from the Google people...

Dan Morrill

unread,
May 16, 2007, 10:22:40 AM5/16/07
to Google-We...@googlegroups.com

Hi, Ian!

I've checked on this explicitly in the past (in Reinier's case, in fact), and electronic CLA submissions are okay.

- Dan Morrill

Dan Morrill

unread,
May 16, 2007, 10:49:09 AM5/16/07
to Google-We...@googlegroups.com

I agree with Reinier -- this is great work!  Thanks for doing this.

We'd obviously love to clear up the keyboard event oddities, but a proper revamp was outside the scope of 1.4.  (This, along with moving to a more convenient event handling model, is one of our goals for the next release.)  The fact that you've done all this analysis footwork is absolutely fantastic -- whether we go with a custom listener, translation table, or whatever, this will be extremely useful information.

What I'd ask you to do is go into our issue tracker and enter a new Issue for this, and attach your patch to it.  Please be sure to include a link to this thread!  We'll then make sure it gets attention for the next release.  You can also feel free to open a discussion on the Contributors' list, but just be aware that it may not get much attention until the 1.4 release candidate ships.

- Dan Morrill

On 5/15/07, Reinier Zwitserloot <rein...@gmail.com> wrote:

Once you get this stuff on the rails and once GWT 1.4 is released, I
vote we petition this for inclusion in GWT itself. The frequency of
questions about keyboard shenanigans in here is at about one a week,
so clearly it's something many users run into. The size of the
translation table could be problematic, so it should probably not be
something in the 'standard' keylistener - instead some sort of
keylistener wrapper that you can use at will. That way if you don't
use it, the GWT compiler won't include any of it in the output JS and
size is not much of an issue. Provided you're okay with that of
course.


On May 15, 10:06 pm, "Ian Petersen" <ispet...@gmail.com> wrote:
> I don't know if anyone's still interested in this thread, but I
> managed to do some research with Safari last Friday (May 11, 2007) and
> I was pleasantly surprised.  Safari 2.something seems to implement
> some subset of the DOM Level 3 Events Specification

> important bit is that key events have a keyIdentifier attribute

Ian Petersen

unread,
May 18, 2007, 2:46:39 PM5/18/07
to Google-We...@googlegroups.com
On 5/16/07, Dan Morrill <morr...@google.com> wrote:
> I agree with Reinier -- this is great work! Thanks for doing this.

Thanks! I hope the work pays off and someone can benefit from it.

> We'd obviously love to clear up the keyboard event oddities, but a proper
> revamp was outside the scope of 1.4. (This, along with moving to a more
> convenient event handling model, is one of our goals for the next release.)
> The fact that you've done all this analysis footwork is absolutely fantastic
> -- whether we go with a custom listener, translation table, or whatever,
> this will be extremely useful information.

Yeah, I realize 1.4 is running a little long, so I don't really expect
anything. I especially don't expect anything because I have no clue
how to write an automated test case.

> What I'd ask you to do is go into our issue tracker and enter a new Issue
> for this, and attach your patch to it. Please be sure to include a link to
> this thread! We'll then make sure it gets attention for the next release.
> You can also feel free to open a discussion on the Contributors' list, but
> just be aware that it may not get much attention until the 1.4 release
> candidate ships.

I will do as you suggest and create an issue. I'll also make sure to
link it to here and here to it. One concern I have is that I'm not
really sure how to make my changes "jive" with GWT, so I'm not sure
how to format a patch. In this post I'm going to explain the work I
did, explain the caveats I've found, and post the code as I am using
it now. Then I'll go create an issue and see if I can't rustle up a
patch. Then I guess we go from there.

So, onwards. My test systems are:

Linux:
- Intel Core 2 Duo @ 2 GHz
- 32-bit Gentoo Linux with kernel 2.6.20, glibc 2.5, and gcc 4.1.1
- Firefox 2.0.0.3 (compiled from source--but I doubt that matters)
- Opera 9.20
- IE 6.0.2800.1106 SP1 running in Wine 0.9.29
- it's a laptop so I have no numpad
- you can have the other specs if you care, but I doubt they're relevant

Windows:
- Intel Pentium 4 @ 2.4 GHz
- Windows 2000, SP4
- Firefox 2.0.0.3
- Opera 9.20
- IE 6.0.2800.1106 SP1
- the keyboard's nothing special, but it does have a numpad
- I didn't try to change the keyboard settings 'cause it's a mission-critical
machine--I assume it's set to US 105-key, or whatever
- you can have more specs if you care

Mac:
- PowerPC G5 @ 1.6 GHz
- Mac OS 10.4.9
- Firefox 2.0.0.3
- Opera 9.20
- Safari 2.0.4
- I assume the keyboard's standard, but I can't tell
- the F-keys go to F16 and there's no NumLock
- you can have more specs if you ask, but you'll have to
tell me how to find the information 'cause I'm a Mac newb

My testing procedure:

- compile the example code below and host it from within JBoss on Linux
- run the test app in the target browser, hit every key combination I can
think of and record whether or not it's properly categorized

The results:

Firefox/Linux:
- it can recognize most keys except the Windows logo key
- it recognizes € (which I type with Right Alt + 5) but not composed
keys like á (which I type with Right Ctrl + ', a)
- it recognizes the Menu key
- it recognizes Alt, Ctrl, Shift, CapsLock, NumLock, and Pause/Break
as individual keys but blocking them doesn't do anything
- it can block everything else that it can recognize (you can even
block Ctrl + w, which is supposed to close the current tab, and
Ctrl + r or F5, which are supposed to refresh the current page)

Opera/Linux
- doesn't recognize the Windows key or the Menu key
- CapsLock is "recognized" as keyCode 0
- NumLock is "recognized" as a regular key with code 144--this doesn't
look like it can be fixed
- it recognizes all the F-keys, but their built-in behaviour can't be blocked
- it can recognize and block € and all the composed keys (typed the
same way as in Firefox)
- it's impossible to recognize Home, End, Insert, or Delete (as previously
mentioned, they look like $, #, -, and ., respectively)
- it seems control keys in general can't be blocked
- everything else can be blocked

IE/Wine/Linux
- it can recognize everything I threw at it except the Windows key
- it recognizes € and composed keys
- it can't block F-keys that have a function
- it can't block the Menu key (although, as an aside, I'm pretty sure
that catching onContextMenu and blocking that works for both the
right mouse button and the Menu key)

Firefox/Windows
- works like Firefox/Linux
- it can't block the Menu key
- characters entered via Alt + Numpad sequence are not recognized

Opera/Windows
- works like Opera/Linux
- characters entered via Alt + Numpad sequence can be recognized
and blocked
- you can't detect the Alt key on its own because it immediately
activates the menu
- the +, -, /, and * keys on the numpad generate doubled keystrokes
(lacking a numpad, I couldn't test this on Linux)

IE/Windows
- works like IE/Wine/Linux
- Alt + Numpad sequences are recognized and can be blocked

Firefox/Mac
- works like Firefox/Linux
- can't block F-keys or Ctrl + foo for combinations that mean something
to the browser (e.g. F5 always causes refresh)
- it can't recognize composed keys (like Option + n, n)
- it can recognize and block extended keys like Option + a
- it can't prevent arrow keys from navigating existing text or
backspace from deleting existing text

Opera/Mac
- works like Opera/Linux
- it can recognize and block extended keys like Option + a
- it can't recognize composed keys like Option + n, n
- Control is 0
- Option, Shift, and Command can all be recognized
- CapsLock can't be recognized

Safari/Mac
- it can recognize and block just about every key, including extended
keys like Option + a
- it can't recognize F-keys that are special to the OS
- it doesn't properly recognize composed keys, but it _can_ block them
(e.g. Option + n, n should produce ñ, but it produces the key sequence
keycode 0, n. You can still block it, though, and if you don't, it does put
an ñ in the text box.)
- it can't recognize Control, Option, Shift, Command, or CapsLock

The code follows my signature. The basic premise is that KeyFilter is
the public-facing interface. It has an onBrowserEvent that you pass a
keyboard event object to and it does the right thing. The various
subclasses of KeyFilterImpl implement the browser-specific code. You
need some magic in your module.gwt.xml file to make the compiler
substitute the right code in the right place.

I've also included the code for a ValidatingTextBox that I used to
test all this code and a sample EntryPoint. You can use all the code
I've posted for whatever purpose you like, but I give no guarantee
that it won't try to shave your cat, unplug your fridge, hit on your
spouse, and/or format your hard drive. It works for me. YMMV.

When I get the issue created in the issue tracker I'll post here with
the issue number.

Ian

// KeyFilter -- the public piece
package com.example.gwt.client.ui;

import com.google.gwt.core.client.GWT;
import com.google.gwt.user.client.DOM;
import com.google.gwt.user.client.Event;
import com.google.gwt.user.client.ui.Widget;

import com.example.gwt.client.ui.impl.KeyFilterImpl;

public abstract class KeyFilter {

public static final char KEY_ALT = 18;
public static final char KEY_ARROW_DOWN = 40;
public static final char KEY_ARROW_LEFT = 37;
public static final char KEY_ARROW_RIGHT = 39;
public static final char KEY_ARROW_UP = 38;
public static final char KEY_BACK_SPACE = 8;

// this key is 0 in Opera/Linux

public static final char KEY_CAPS_LOCK = 20;


public static final char KEY_CTRL = 17;
public static final char KEY_DELETE = 46;
public static final char KEY_END = 35;
public static final char KEY_ESC = 27;
public static final char KEY_F1 = 112;
public static final char KEY_F10 = 121;
public static final char KEY_F11 = 122;
public static final char KEY_F12 = 123;
public static final char KEY_F2 = 113;
public static final char KEY_F3 = 114;
public static final char KEY_F4 = 115;
public static final char KEY_F5 = 116;
public static final char KEY_F6 = 117;
public static final char KEY_F7 = 118;
public static final char KEY_F8 = 119;
public static final char KEY_F9 = 120;
public static final char KEY_HOME = 36;
public static final char KEY_INSERT = 45;

// this key can't be trapped in Opera/Linux

public static final char KEY_MENU = 93;


public static final char KEY_NUMLOCK = 144;
public static final char KEY_PAGE_DOWN = 34;
public static final char KEY_PAGE_UP = 33;
public static final char KEY_PAUSE_BREAK = 19;
public static final char KEY_RETURN = 13;
public static final char KEY_SHIFT = 16;
public static final char KEY_TAB = 9;
// this key is 0 in Firefox/Linux
// this key can't be trapped in Opera/Linux
// the value 225 was derived from IE6/Wine

// on reflection, this constant might be useless


public static final char KEY_WINDOWS = 225;

private KeyFilterImpl impl;

public KeyFilter() {
impl = (KeyFilterImpl) GWT.create(KeyFilterImpl.class);
}

public final void onBrowserEvent(Widget sender, Event event) {
switch (DOM.eventGetType(event)) {
case Event.ONKEYDOWN:
impl.onKeyDown(this, sender, event);
break;
case Event.ONKEYPRESS:
impl.onKeyPress(this, sender, event);
break;
default:
break;
}
}

/**
* Called when a control key is pressed in an observed widget. Note

* that some control keys cannot be blocked in some browsers or
* on some platforms.


*
* @param sender the widget that generated the keypress
* @param keyCode the keycode for the key that was pressed
* @param modifiers the modifier keys that were depressed when the

* event occurred. This value is a combination of the bits
* defined by <code>{@link


KeyboardListener#MODIFIER_SHIFT}</code>,
* <code>{@link KeyboardListener#MODIFIER_CTRL}</code>, and
* <code>{@link KeyboardListener#MODIFIER_ALT}</code>.
* @return <code>true</code> to let the key pass or

* <code>false</code> to block the key
*/
public abstract boolean onControlKey(Widget sender, char keyCode,
int modifiers);

/**
* Called when a non-control key is pressed in an observed widget.
*
* @param sender the widget that generated the keypress
* @param keyCode the Unicode character generated by the key that
was pressed
* @param modifiers the modifier keys that were depressed when the

* event occurred. This value is a combination of the bits
* defined by <code>{@link


KeyboardListener#MODIFIER_SHIFT}</code>,
* <code>{@link KeyboardListener#MODIFIER_CTRL}</code>, and
* <code>{@link KeyboardListener#MODIFIER_ALT}</code>.
* @return <code>true</code> to let the key pass or

* <code>false</code> to block the key
*/
public abstract boolean onRegularKey(Widget sender, char keyCode,
int modifiers);
}

// KeyFilterImpl -- the super class for the browser-specific bits
package com.example.gwt.client.ui.impl;

import com.google.gwt.user.client.DOM;
import com.google.gwt.user.client.Event;
import com.google.gwt.user.client.ui.KeyboardListener;
import com.google.gwt.user.client.ui.Widget;

import com.example.gwt.client.ui.KeyFilter;

public abstract class KeyFilterImpl {

public abstract void onKeyDown(KeyFilter filter, Widget sender,
Event event);

public abstract void onKeyPress(KeyFilter filter, Widget sender,
Event event);

protected static void cancelKey(Event event) {
DOM.eventPreventDefault(event);
}

protected static char getKeyCode(Event event) {
return (char) DOM.eventGetKeyCode(event);
}

protected static int getModifiers(Event event) {
return (DOM.eventGetShiftKey(event) ?
KeyboardListener.MODIFIER_SHIFT : 0)
| (DOM.eventGetCtrlKey(event) ? KeyboardListener.MODIFIER_CTRL : 0)
| (DOM.eventGetAltKey(event) ? KeyboardListener.MODIFIER_ALT : 0);
}
}

// KeyFilterImplIE6 -- the IE6-specific code
package com.example.gwt.client.ui.impl;

import com.google.gwt.user.client.Event;
import com.google.gwt.user.client.ui.Widget;

import com.example.gwt.client.ui.KeyFilter;

public final class KeyFilterImplIE6 extends KeyFilterImpl {

private boolean skipKeyPress;

private boolean isControlKey(Event event) {
char keyCode = getKeyCode(event);

return (keyCode <= KeyFilter.KEY_ARROW_DOWN && keyCode != 32)


// 32 is the spacebar

|| (keyCode == KeyFilter.KEY_INSERT)
|| (keyCode == KeyFilter.KEY_DELETE)
|| (keyCode == KeyFilter.KEY_MENU)
|| (KeyFilter.KEY_F1 <= keyCode && keyCode <= KeyFilter.KEY_F12)
|| (keyCode == KeyFilter.KEY_NUMLOCK)
|| (keyCode == KeyFilter.KEY_WINDOWS);
}

public void onKeyDown(KeyFilter filter, Widget sender, Event event) {
if (isControlKey(event)) {
char keyCode = getKeyCode(event);
int modifiers = getModifiers(event);

if (!filter.onControlKey(sender, keyCode, modifiers)) {
cancelKey(event);
}

skipKeyPress = true;
}
else {
skipKeyPress = false;
}
}

public void onKeyPress(KeyFilter filter, Widget sender, Event event) {
if (skipKeyPress) {
return;
}

char keyCode = getKeyCode(event);
int modifiers = getModifiers(event);

if (!filter.onRegularKey(sender, keyCode, modifiers)) {
cancelKey(event);
}
}
}

// KeyFilterImplMoz -- the Gecko-specific code
package com.example.gwt.client.ui.impl;

import com.google.gwt.user.client.Event;
import com.google.gwt.user.client.ui.Widget;

import com.example.gwt.client.ui.KeyFilter;

public final class KeyFilterImplMoz extends KeyFilterImpl {

private static native char getEventCharCode(Event event) /*-{
return event.charCode;
}-*/;

private static native char getEventKeyCode(Event event) /*-{
return event.keyCode;
}-*/;

private static boolean isKeyDownControlKey(char keyCode) {
return (keyCode <= KeyFilter.KEY_ARROW_DOWN && keyCode != 32)


// 32 is the spacebar

|| (keyCode == KeyFilter.KEY_INSERT)
|| (keyCode == KeyFilter.KEY_DELETE)
|| (keyCode == KeyFilter.KEY_MENU)
|| (KeyFilter.KEY_F1 <= keyCode && keyCode <= KeyFilter.KEY_F12)
|| (keyCode == KeyFilter.KEY_NUMLOCK);
}

private static boolean isKeyPressControlKey(Event event) {
return getEventCharCode(event) < 32;
}

private boolean keyHandledInOnKeyDown;

public void onKeyDown(KeyFilter filter, Widget sender, Event event) {
char keyCode = getEventKeyCode(event);

// if the keyCode is 0, let the key pass until keyPress to
work with Firefox on Mac
keyHandledInOnKeyDown = (keyCode != 0 && isKeyDownControlKey(keyCode));

if (!keyHandledInOnKeyDown) {
return;
}

boolean permitKey = filter.onControlKey(sender, keyCode,
getModifiers(event));

if (!permitKey) {
cancelKey(event);
}
}

public void onKeyPress(KeyFilter filter, Widget sender, Event event) {
if (keyHandledInOnKeyDown) {
return;
}

int modifiers = getModifiers(event);

boolean permitKey;

if (isKeyPressControlKey(event)) {
permitKey = filter.onControlKey(sender,
getEventKeyCode(event), modifiers);
}
else {
permitKey = filter.onRegularKey(sender,
getEventCharCode(event), modifiers);
}

if (!permitKey) {
cancelKey(event);
}
}
}

// KeyFilterImplOpera -- the Opera-specific code
package com.example.gwt.client.ui.impl;

import com.google.gwt.user.client.Event;
import com.google.gwt.user.client.ui.Widget;

import com.example.gwt.client.ui.KeyFilter;

public final class KeyFilterImplOpera extends KeyFilterImpl {

public void onKeyDown(KeyFilter filter, Widget sender, Event event) {
}

public void onKeyPress(KeyFilter filter, Widget sender, Event event) {
int modifiers = getModifiers(event);

boolean permitKey;

if (isControlKey(event)) {
permitKey = filter.onControlKey(sender,
getEventKeyCode(event), modifiers);
}
else {
permitKey = filter.onRegularKey(sender,
getEventCharCode(event), modifiers);
}

if (!permitKey) {
cancelKey(event);
}
}

private static native char getEventCharCode(Event event) /*-{
return event.which;
}-*/;

private static native char getEventKeyCode(Event event) /*-{
return event.keyCode;
}-*/;

private static boolean isControlKey(Event event) {
return getEventCharCode(event) < 32;
}
}

// KeyFilterImplSafari -- the Safari-specific code
package com.example.gwt.client.ui.impl;

import com.google.gwt.user.client.Event;
import com.google.gwt.user.client.ui.Widget;

import com.example.gwt.client.ui.KeyFilter;

public final class KeyFilterImplSafari extends KeyFilterImpl {

public void onKeyDown(KeyFilter filter, Widget sender, Event event) {
}

public void onKeyPress(KeyFilter filter, Widget sender, Event event) {
int modifiers = getModifiers(event);
char keyCode = getKeyCode(event);

boolean permitKey;

if (isControlKey(event)) {
permitKey = filter.onControlKey(sender, keyCode, modifiers);
}
else {
permitKey = filter.onRegularKey(sender, keyCode, modifiers);
}

if (!permitKey) {
cancelKey(event);
}
}

protected static native char getKeyCode(Event e) /*-{
return e.charCode;
}-*/;

private static native boolean isControlKey(Event e) /*-{
var re = /^U\+[0-9a-fA-F]*$/;

if (re.test(e.keyIdentifier)) {
return parseInt(e.keyIdentifier.substr(2), 16) < 32;
}

return false;
}-*/;
}

<!-- you need the following magic in a module.gwt.xml to make the
above KeyFilterImpl classes work properly -->
<replace-with class="com.example.gwt.client.ui.impl.KeyFilterImplOpera">
<when-type-is class="com.example.gwt.client.ui.impl.KeyFilterImpl"/>
<when-property-is name="user.agent" value="opera"/>
</replace-with>

<replace-with class="com.example.gwt.client.ui.impl.KeyFilterImplIE6">
<when-type-is class="com.example.gwt.client.ui.impl.KeyFilterImpl"/>
<when-property-is name="user.agent" value="safari"/>
</replace-with>

<replace-with class="com.example.gwt.client.ui.impl.KeyFilterImplIE6">
<when-type-is class="com.example.gwt.client.ui.impl.KeyFilterImpl"/>
<when-property-is name="user.agent" value="ie6"/>
</replace-with>

<replace-with class="com.example.gwt.client.ui.impl.KeyFilterImplMoz">
<when-type-is class="com.example.gwt.client.ui.impl.KeyFilterImpl"/>
<any>
<when-property-is name="user.agent" value="gecko1_8"/>
<when-property-is name="user.agent" value="gecko"/>
</any>
</replace-with>

// ValidatingTextBox -- an abstract Widget to be used as the basis for
// text boxes that filter their keystrokes
package com.example.gwt.client.ui;

import com.google.gwt.user.client.DOM;
import com.google.gwt.user.client.Event;
import com.google.gwt.user.client.ui.ChangeListener;
import com.google.gwt.user.client.ui.ClickListener;
import com.google.gwt.user.client.ui.ClickListenerCollection;
import com.google.gwt.user.client.ui.Composite;
import com.google.gwt.user.client.ui.FocusListener;
import com.google.gwt.user.client.ui.FocusListenerCollection;
import com.google.gwt.user.client.ui.HasName;
import com.google.gwt.user.client.ui.HasText;
import com.google.gwt.user.client.ui.SourcesChangeEvents;
import com.google.gwt.user.client.ui.SourcesClickEvents;
import com.google.gwt.user.client.ui.SourcesFocusEvents;
import com.google.gwt.user.client.ui.TextBox;
import com.google.gwt.user.client.ui.Widget;

public abstract class ValidatingTextBox extends Composite
implements SourcesChangeEvents
, SourcesClickEvents
, SourcesFocusEvents
, HasText
, HasName {

private final class InnerTextBox extends TextBox {

private final KeyFilter filter;

public InnerTextBox() {
filter = new KeyFilter() {
public boolean onControlKey(Widget sender, char
keyCode, int modifiers) {
return validateControlKey(sender, keyCode, modifiers);
}

public boolean onRegularKey(Widget sender, char
keyCode, int modifiers) {
return validateRegularKey(sender, keyCode, modifiers);
}
};
}

public void onBrowserEvent(Event event) {
switch (DOM.eventGetType(event)) {
case Event.ONKEYDOWN:
case Event.ONKEYPRESS:
filter.onBrowserEvent(ValidatingTextBox.this, event);
break;
default:
super.onBrowserEvent(event);
break;
}
}
}

private ClickListenerCollection clickListeners = new
ClickListenerCollection();
private FocusListenerCollection focusListeners = new
FocusListenerCollection();
private String previousText = "";
private TextBox textBox = new InnerTextBox();

public ValidatingTextBox() {
textBox.addChangeListener(new ChangeListener() {
public void onChange(Widget sender) {
String text = doGetText();

if (validateText(text)) {
handleTextChanged(text);
}
else {
doSetText(previousText);
}
}
});

textBox.addFocusListener(new FocusListener() {
public void onFocus(Widget sender) {
previousText = doGetText();
focusListeners.fireFocus(ValidatingTextBox.this);
}

public void onLostFocus(Widget sender) {
focusListeners.fireLostFocus(ValidatingTextBox.this);
}
});

textBox.addClickListener(new ClickListener() {
public void onClick(Widget sender) {
clickListeners.fireClick(ValidatingTextBox.this);
}
});

initWidget(textBox);
}

public final void addClickListener(ClickListener listener) {
clickListeners.add(listener);
}

public final void addFocusListener(FocusListener listener) {
focusListeners.add(listener);
}

public final String getName() {
return textBox.getName();
}

public final int getTabIndex() {
return textBox.getTabIndex();
}

public String getText() {
return doGetText();
}

public final void removeClickListener(ClickListener listener) {
clickListeners.remove(listener);
}

public final void removeFocusListener(FocusListener listener) {
focusListeners.remove(listener);
}

public final void setAccessKey(char key) {
textBox.setAccessKey(key);
}

public final void setFocus(boolean focused) {
textBox.setFocus(focused);
}

public final void setName(String name) {
textBox.setName(name);
}

public final void setTabIndex(int index) {
textBox.setTabIndex(index);
}

public void setText(String text) {
if (validateText(text)) {
doSetText(text);
}
else {
throw new IllegalArgumentException("Invalid text: '" + text + "'");
}
}

protected final String doGetText() {
return textBox.getText();
}

protected final void doSetText(String text) {
textBox.setText(text);
}

protected abstract boolean validateControlKey(Widget sender, char
keyCode, int modifiers);

protected abstract boolean validateRegularKey(Widget sender, char
keyCode, int modifiers);

protected abstract boolean validateText(String text);

protected abstract void handleTextChanged(String text);
}

// The following is the entry point I used to test everything
public final class TestEntryPoint implements EntryPoint {

public void onModuleLoad() {
RootPanel.get().add(new Foo());
}

private static final class Foo extends VerticalPanel {
public Foo() {

ValidatingTextBox textBox = new ValidatingTextBox() {
protected void handleTextChanged(String text) {
}

protected boolean validateControlKey(Widget sender,
char keyCode, int modifiers) {


insert(new Label("Control Key: " + (int) keyCode), 1);
return false; // try to block the key
}

protected boolean validateRegularKey(Widget sender,
char keyCode, int modifiers) {


insert(new Label("Regular Key: '" + keyCode + "'
(" + (int) keyCode + ")"), 1);
return false; // try to block the key
}

protected boolean validateText(String text) {
return false;
}

public void addChangeListener(ChangeListener listener) {
}

public void removeChangeListener(ChangeListener listener) {
}
};

add(textBox);

Ian Petersen

unread,
May 18, 2007, 3:12:48 PM5/18/07
to Google-We...@googlegroups.com
Reply all
Reply to author
Forward
0 new messages