Skip to main content

Variations of JPasswordField

Posted 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 feedback

JPasswordField 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 space

For 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:

  • Layout of either text or child Views. This includes calculating
    a size for the View.

  • Painting.
  • Provide a mapping between model and view, or view and model. For
    example, what position on screen is the character at location 5 in the
    model.

  • Updating as the model changes.

To implement the variable width password field we'll provide a custom
View implementation,
VariableWidthPasswordView. VariableWidthPasswordView will keep a List
of integers that correspond to the width of each characters. An
instance of Random will be used to determine the amount of white space
to use for a particular character:

    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 href="http://weblogs.java.net/blog/zixle/archive/2005.12.05/VariableWidthPasswordView.java">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 href="http://java.sun.com/docs/books/tutorial/uiswing/components/text.html">Using
Text Components or Tim's article on href="http://java.sun.com/products/jfc/tsc/articles/text/overview/index.html">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 >>