Blogs / Tech Blog

Stupid fast hyperlinks for Swing

Co-authored by Huey.

HyperLinkLabel screen shot
Most people who’ve done a little preliminary looking online will learn that to create a component that functions like a hyperlink the easiest way is to use a JEditorPane. If you use the HTMLEditorKit you can introduce hyperlinks, they’ll render appropriately, and you can even add a HyperlinkListener. There’s just one drawback. It’s a little slow to instantiate. Alternatively, you can pass html to a JLabel, which will render the hyperlink, then add a mouse listener, but that’s not much faster. Besides that, the JEditorPane doesn’t seem to alter the mouse when you mouseover an active link. Sloppy. So I wrote HyperlinkLabel.

Try out the Web Start HyperlinkLabel Demo | Download executable jar + source

Take a look at these numbers for 1000 instantiations of a configured class:

JEditorPane: 1922 ms, 1906 ms, 1922 ms = Average 1916 ms
JLabel: 1250 ms, 1250 ms, 1234 ms = Average 1244 ms
HyperlinkLabel: 62 ms, 62 ms, 63 ms = Average 62.3 ms

Read on for more details.

Motivation

I suppose the amount of time you’re seeing above doesn’t seem too bad: 2 ms per editor pane instantiation isn’t so bad. But we have a lot of small hyperlink components, and my perf testing indicated it was burning up around 5% of our runtime in one workflow, so I figured why not make it as efficient as possible?

Also, the user experience wasn’t as good as it could have been – I wanted the hand cursor when mousing over the link to indicate to the user that the link is clickable. In particular, I only wanted the hand cursor when the mouse is over the link text, not just when the mouse is somewhere over the JLabel.

Painting the hyperlink

HyperlinkLabel permits one link embedded in a larger string, so the HyperlinkLabelUI obtains the prefix text, link text, and suffix text, then paints each of the three parts in appropriate color. The LineMetrics for the text supplies the offset from baseline at which to draw underlines. I found that the underline offset for font sizes I generally worked with was 0.7-ish and if I truncated to int in order to draw a line, it drew the line directly at the baseline. This turned out to be unattractive, so I applied Math.ceil() to round up.

In order to paint the text, the UI is told exactly where the text appears. This is computed via BasicLabelUI.layoutCL(lotsa args), a protected method. In order not to copy too much code around and duplicate layout computations, HyperlinkLabelUI tells HyperlinkLabel where the text is so mouseover/mouseclicks can be computed correctly. Since this updates on every paint, it’ll be accurate when a mouse event is delivered.

Using HyperlinkLabels

In most respects, it’s a JLabel – you can fiddle with the alignment, add an icon, etc. Just don’t try to set the text via HTML, or it won’t work quite right. A common pattern might be:

    HyperlinkLabel hll = new HyperlinkLabel();
    hll.setLinkText("Click for a tasty ", "snack", ".", Color.WHITE);
    hll.addActionListener(this);

Java 6.0 Compatibility

Well, Alert Reader Garry notified me that Java 6.0 not so happy with previous implementation — I called SwingUtilities2 methods directly, an apparent no-no, because Sun moved SwingUtilities2 to the com.sun package in that release. So I managed to work around that. Now I call super.paintEnabledText(), sequentially passing it the prefix, link text, and suffix with appropriate coordinates. The only trick I played was getting HyperlinkLabel to lie about its foreground color because the implementation of BasicLabelUI.paintEnabled is this:

        int mnemIndex = l.getDisplayedMnemonicIndex();
        g.setColor(l.getForeground());
        SwingUtilities2.drawStringUnderlineCharAt(l, g, s, mnemIndex, textX, textY);

So I had to make sure JLabel.getForeground() returned blue when I used paintEnabledText to paint the link text.

Wrapup

That’s all for today. At some point I might like the label to change the link color for “visited links” and it might be nice if it behaved a bit more like JButton — mouse press arms and mouse release fires. Firing only on mouse click means if you accidentally drag a bit after pressing the mouse button the link won’t fire. Nor does it visibly react to the mousePressed since it doesn’t have an armed state.

-Carl

Other Blogs