Friday, June 23, 2006

Skipping read-only text components in Swing with TAB

After a superficial web search, I did not turn up a simple way (read cut and paste) to TAB over read-only components while navigating a container in Swing. JComboBox, JTextComponent and JTree all have an isEditable/setEditable(boolean) pair, however it has different meaning for each of them.

The case I am interested in JTextComponent. For text components, uneditable is equivalent to read-only: you can select the text from the component but cannot change it. The alternative is to disable the component, but then the text is not selectable for copying.

So, given a uneditable text component, what happens when I traverse through a form with the TAB key? Unfortunately, the JDK by default will move the focus (the caret) into the read-only text field even though you cannot type anything. Fixing the form to skip over read-only text fields is straight-forward but not directly documented:

public static void skipReadOnlyTextComponents(
        final Container container) {
    // Without this, JDK ignores policy
    container.setFocusCycleRoot(true);
    container.setFocusTraversalPolicy(new MyPolicy());
}

class MyPolicy extends LayoutFocusTraversalPolicy {
    @Override
    protected boolean accept(final Component c) {
        return !isReadOnly(c) && super.accept(c);
    }

    private static boolean isReadOnly(
            final Component c) {
        if (c instanceof JTextComponent)
            return !((JTextComponent) c).isEditable();

        return false;
    }
}

In broader use, it is worthwhile to make an Editable interface for isEditable/setEditable and add that to the instanceof tests in isReadOnly.

Wednesday, June 21, 2006

Answer: Using JFormattedTextField for integers

I wrote earlier about using JFormattedTextField for integers and the difference between using an initial value of Integer (rounds input) v. BigInteger (rejects invalid input). I also wrote that I did not understand why there was a difference.

I now know what happened.

The culprit for handling Integer turns out to be NumberFormat and friends. The rounding behavior is not numerical rounding at all; it is truncating the input text.

The parser for the input stops after finding valid input and returns a parsed value in stringToValue(String):Object. This discards any trailing text, which in the case of parsing an integer is everything from the decimal point forward. The same behavior can be seen with a date parser: Jun 21, 2006xxx parses to Jun 21, 2006 and the xxx is discarded.

The solution to enforce correct input even with trailing garbage is cumbersome: initialize JFormattedTextField with a custom formatter and check that parsed input deparses back cleanly:

new JFormattedTextField(new Integer(0))

becomes:

new JFormattedTextField(new NumberFormatter(
        NumberFormat.getIntegerInstance()) {
    @Override
    public Object stringToValue(final String text)
            throws ParseException {
        // Throws if the input is corrupt from the start
        final Object parsed = super.stringToValue(text);
        final String deparsed = valueToString(parsed);

        // Throws if there is no clean roundtrip,
        // such as trailing garbage characters
        // For date parsing, etc., consider equalsIgnoreCase
        if (!deparsed.equals(text))
            throw new ParseException(text, deparsed.length());

        return parsed;
    }
}) {
    {
        // Initialize to the original starting value
        setValue(new Integer(0));
    }
}

(Yes, the anonymous instance syntax is awkward here.)

That's a lot of work to achieve what seems like a simple goal: actually valid input for a JFormattedTextField.

UPDATE: Given the hoops to jump through for this more "correct" solution, I think I'll stick to new JFormattedTextField(new BigInteger(0)) for now, although that does nothing to help with other input types such as dates.

Tuesday, June 20, 2006

Using JFormattedTextField for integers

I discovered the hard way that:

new JFormattedTextField(new Integer(0))

does not do what I wanted.

What I wanted was to have a text entry field restricted to integers. What I got was a field which rounded off numbers to integers. This was surprising.

After a while of poking at it, I tried using various incarnations of NumberFormat and DecimalFormat in the field constructor with no luck. Perusing the sources was not particularly illuminating either, at least at first.

Finally I noticed that JFormattedTextField.getDefaultFormatterFactory(Object) contained this legerdemain:

if (type instanceof DateFormat) { ... }
if (type instanceof NumberFormat) { ... }
if (type instanceof Format) { ... }
if (type instanceof Date) { ... }
if (type instanceof Number) { /* mucking around with formatter factory */ }

Hmmm. So in a flash of inspiration I tried:

new JFormattedTextField(new BigInteger(0))
and it worked, although I do not entirely understand why. JFormattedTextField maintains separately value, formatter and formatter factory in addition to the document model for text entry and a lot of state options for handling valid and invalid input.

I'm sure its a tour de force for someone at Sun, and most importantly the class works, but quirks like this one are difficult to find and explain.

UPDATE: A colleague of mine asked for more details on this point. To clarify:

Argument Input Behavior
new Integer(0) 3.0 3
3.3 3
new BigInteger(0) 3.0 3
3.3 invalid

So to get non-integral input to be treated as invalid, you must initialize JFormattedTextField with a BigInteger; initializing with an Integer gets a rounding mode instead.

Monday, June 19, 2006

Programmatically show a tool tip in Swing

A better way to do this exists, I am sure, but I failed to find it easily with Google: How to display a tool tip programmatically in Swing:

ToolTipManager.sharedInstance().mouseMoved(
        new MouseEvent(targetComponent, 0, 0, 0,
                0, 0, // X-Y of the mouse for the tool tip
                0, false));

Note the commented line. This is the relative location of the synthetic mouse event to the target component. The (0, 0) example above is as if the user had paused the mouse at the upper-left corner of the component, and the tool tip displays accordingly.

Another choice might be (targetComponent.getWidth(), targetComponent.getHeight()) for the lower-right corner, etc.

I wish Swing had a more straight-forward way of using the tool tip framework programmatically. I would like to be able to reuse tool tips for form validation. But at least this works.

UPDATE: 4 years on and I still get more positive comments on this post than any other. Hopefully the JDK improves in this area.

Monday, June 12, 2006

ViM 7

In another salvo in the endless vi v. Emacs war, the ViM folks released ViM 7.0.

The announcement is about a month old. Why the delay for me mentioning it? Cygwin just updated their 6.4 to 7.0 over the weekend. Too bad the update removed vi from /usr/bin. Oops.

UPDATE: I found one source of my Cygwin trouble. For whatever reason, the ViM 7.0 installation put vim.exe and friends into C:\cygwin\usr\bin instead of /usr/bin. As my default Cygwin installation mounts C:\cygwin\bin as /usr/bin, that means the mount hides the files in the underlying Windows directory:

C:\cygwin\bin -> /usr/bin
C:\cygwin\usr\bin -> hidden underneath

A quick unmount, switch to CMD.EXE, files moved to the right Windows directory, and remount: I can now run vim.

One remaining problem: whither the vi command?

UPDATE: Run the Cygwin installer/updater one more time. There is a patch up for the new ViM 7 package which fixes the missing /usr/bin/vi problem and the overall install issues.

Friday, June 09, 2006

Google Browser Sync

Well, this looks interesting.

Finding a field in Java

A large motivation for me starting this blog was to help me remember useful snippets of code. This is a good example:

Field getField(Class clazz, final String fieldName)
        throws NoSuchFieldException {
    for (; null != clazz; clazz = clazz.getSuperclass()) {
        try {
            final Field field = clazz.getDeclaredField(fieldName);
            field.setAccessible(true);
            return field;
        } catch (final NoSuchFieldException e) { }
    }

    throw new NoSuchFieldException(fieldName);
}

Very simple code, no? But a fact I seem to sometime forget is that inherited fields do not show up in reflection; one must reflect down into the appropriate superclass where the field was declared. The simple method handles the details easily enough.

Thursday, June 08, 2006

Against agilism

As much as I am a fan of agile development methodologies, I must admit that Cedric Beust has some good points against agile.

I need more time to think his post through. It is meaty, lengthy and well-written. It is also a bit abrasive, but I enjoy that style.

Wednesday, June 07, 2006

IntelliJ IDEA build 5321

After several builds that gave me a lot of trouble, IntelliJ IDEA build 5321 seems to hit the spot.

With several of the previous EAP builds the IDE leaked memory fairly badly and I often hit mysterious lock ups for minutes at a time. The only solution was restarting. Worse was that many times the dialog to mail exception reports to JetBrains itself would fail.

Build 5321 does not seem to have these flaws, happily, and seems robust enough for daily use.