Search |
||
Variations of JPasswordFieldPosted by zixle on December 5, 2005 at 3:37 PM PST
We've been gathering input from customers on various features they
would like to see us implement. At such a meeting one customer
requested a variation of JPasswordField. I figured it
would be interesting to blog on how you could modify JPasswordField to
vary the level of feedback it gives you. I'll cover various pieces of
Swing's text architecture in implementing one variation, as well as a
demo. If you want to cut to the chase, and have 1.5 installed, here's
the demo.
Variation 1: no feedbackJPasswordField allows you to change the character it uses as the echo character. That is, regardless of what character the user types you'll see the echo character in it's place (1). A simple variation on JPasswordField that makes it harder for folks to guess at what you're typing is to change the echo character to a character that takes up no space. You can use '\u200b' for this. This means that someone watching you type at a password field won't be able to tell how many characters are in your password. Of course they can still watch you type at the keyboard, and the user gets very little feedback that they're actually typing. Here's the code for this:
passwordField.setEchoCharacter('\u200b');
Variation 2: variable amount of spaceFor those that remember NeXT's login dialog this'll be familiar. Rather than using a single echo character a random amount of white space will be used for each character. This has the benefit over the previous approach in that you can't readily determine how many characters are in the password, but you get some level of visual feedback as you type your password. Apple's latest login dialogs don't use this scheme, which would seem to indicate many folks didn't like it.All of Swing's text components, be it JPasswordField, JTextField, JEditorPane share the same architecture. What applies to one equally applies to the others. The text component use an instance of View to do the rendering of text. In some ways a View can be thought of as a lightweight Component, for example, View has methods like getPreferredSpan, getMinimumSpan, paint... Just as Containers are typically nested Views are usually nested as well. Views are responsible doing the following:
private static final int MIN_WIDTH = 2;
private static final int MAX_WIDTH = 15;
private List<Integer> widths;
private Random widthGenerator;
private final int generateCharacterSize() {
return widthGenerator.nextInt(MAX_WIDTH - MIN_WIDTH) + MIN_WIDTH;
}
Swing's text components use Document for modeling the text. As you
type at a text component the text is inserted into the Document. As
the Document is mutated the Views are notified (as long as the
document is associated with a JTextComponent). This allows the Views
to stay in sync with the model. VariableWidthPasswordView will
override the two View methods insertUpdate and removeUpdate, which are
invoked as text is inserted or removed. The implementation of these
two methods is roughly the same, they will update the cache of widths
and trigger a repaint. Here's the implementation of insertUpdate:
// Invoked when text has been inserted. Updates the width List appropriately
public void insertUpdate(DocumentEvent changes, Shape a, ViewFactory f) {
for (int i = 0; i < changes.getLength(); i++) {
// Notice the autoboxing of int to Integer here.
widths.add(i + changes.getOffset(), generateCharacterSize());
}
// Invoke super to update the visibility appropriately.
super.insertUpdate(changes, a, f);
}
Invoking insertUpdate on super is done to update JPasswordFields
visibility model. It will also signal the preferred size of the View
has changed and repaint.
VariableWidthPasswordView doesn't need to paint, so it no-ops the appropriate paint methods. The primary View method for painting is paint. VariableWidthPasswordView is a subclass of FieldView which implements paint to invoke drawSelectedText and drawUnselectedText. VariableWidthPasswordView will override drawSelectedText and drawUnselectedText to do nothing. This is done rather than overriding paint as paint is also responsible for invoking the appropriate methods to render the selection. By overriding drawUnselectedText and drawSelectedText to do nothing the selection is still rendered appropriately by FieldView. To map from a position of text in the model to that of the view VariableWidthPasswordView overrides modelToView. modelToView uses the List of widths to determine the position. Here's the implementation:
public Shape modelToView(int pos, Shape a, Position.Bias b)
throws BadLocationException {
// JPasswordField and JTextField implement their own virtual
// coordinate system. This does the necessary mapping.
Rectangle bounds = adjustAllocation(a).getBounds();
bounds.x += modelToView(pos);
bounds.width = 1;
return bounds;
}
// Returns the view x coordinate in the views space for the specified
// model coordinate.
private final int modelToView(int p1) {
int offset = 0;
int end = Math.min(widths.size(), p1);
for (int i = 0; i < end; i++) {
// Notice the auto unboxing here.
offset += widths.get(i);
}
return offset;
}
viewToModel does the inverse mapping:
public int viewToModel(float fx, float fy, Shape a, Position.Bias[] bias) {
a = adjustAllocation(a);
Rectangle bounds = (a instanceof Rectangle) ? (Rectangle)a : a.getBounds();
int x = (int)fx - bounds.x;
for (int i = 0; i < widths.size(); i++) {
// More auto unboxing.
x -= widths.get(i);
if (x <= 0) {
return i;
}
}
return getEndOffset();
}
The last thing to mention is calculating the preferred size for the
View. That's done in terms of the widths field as well:
public float getPreferredSpan(int axis) {
if (axis == View.X_AXIS) {
int size = 0;
for (Integer width : widths) {
size += width;
}
return size;
}
return super.getPreferredSpan(axis);
}
Now that we have the View implementation we need to modify
JPasswordField to use it. Views are created by a ViewFactory, which is
obtained from the EditorKit. It's up to the TextUI implementation to
provide the EditorKit and ViewFactory. Unfortunately this means
creating a custom UI to plug-in
VariableWidthPasswordView. BasicPasswordFieldUI is itself a
ViewFactory, so that by subclassing BasicPasswordFieldUI we can
plug-in VariableWidthPasswordView. Here's the code for it:
// We generally discourage using UIs from one look and feel
// with another look and feel. When using metal this will be fine,
// that may not be true when using other look and feels.
JPasswordField myPasswordField = new JPasswordField(10) {
public void updateUI() {
setUI(new BasicPasswordFieldUI() {
public View create(Element elem) {
return new VariableWidthPasswordView(elem);
}
});
}
};
The complete source for VariableWidthPasswordView can be found here. And the demo showing the various takes on
password field can be found here. For more
information on Swing's text component take a look at the Swing
tutorial section on Using
Text Components or Tim's article on Using
the Swing Text Package.
1: As this demo shows it's possible to write a UI that ignores the echo character. All of Sun's UIs honor the echo character, but third party look and feels may not. »
Related Topics >>
Open JDK Comments
Comments are listed in date ascending order (oldest first)
|
||
|
|