Swing Hyperlink Labels

JavaFX has a convenient Hyperlink control that’s functionally a button styled as an underlined label, just like an HTML hyperlink. Java Swing has clickable hyperlinks in its JEditorPane control but does not provide them as a separate lightweight control. Fortunately it’s quite easy to roll your own, and that’s what the code below does. JHyperlink extends AbstractButton with the following features:

  • A neutral look without any special button decorations that should match ordinary labels, courtesy of BasicButtonUI.
  • A hand cursor appears when the mouse hovers over the control, and a dashed border appears when the control has keyboard focus.
  • Call setLink to automatically underline a plain text label via HTML tags. Alternatively, specify HTML text for custom decorations.
  • An optional target property can be used to associate arbitrary objects with the control, for example the URL to visit on clicks.

import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.border.Border;
import javax.swing.plaf.basic.BasicButtonUI;

/**
 * Provides an {@link AbstractButton} styled as an HTML hyperlink.
 * @author Christoph Nahr
 * @version 1.0.0
 */
public class JHyperlink extends AbstractButton {
    private static final long serialVersionUID = 0L;

    private final Border _focusBorder, _emptyBorder;
    private Object _target;

    /**
     * Creates a {@link JHyperlink}.
     * Creates an {@link AbstractButton} with default behavior,
     * showing as plain text with a {@link Cursor#HAND_CURSOR}.
     */
    public JHyperlink() {
        super();

        _focusBorder = BorderFactory.createDashedBorder(getForeground());
        final Insets insets = _focusBorder.getBorderInsets(this);
        _emptyBorder = BorderFactory.createEmptyBorder(
                insets.top, insets.left, insets.bottom, insets.right);
        setBorder(_emptyBorder);

        addFocusListener(new JHyperlinkFocusListener());
        setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
        setModel(new DefaultButtonModel());
        setUI(new BasicButtonUI());
    }

    /**
     * Gets the target of the {@link JHyperlink}.
     * Provided for convenience. May return {@code null} and defaults to {@code null}.
     * The {@link JHyperlink} class does not use this value.
     *
     * @return the target of the {@link JHyperlink}
     */
    public Object getTarget() {
        return _target;
    }

    /**
     * Sets the target of the {@link JHyperlink}.
     * Provided for convenience. Arbitrary objects and {@code null} are acceptable.
     * The {@link JHyperlink} class does not use this value.
     *
     * @param target the target of the {@link JHyperlink}
     */
    public void setTarget(Object target) {
        _target = target;
    }

    /**
     * Sets the link text of the {@link JHyperlink}.
     * An empty {@code link} or one that starts with "<" is forwarded to {@link #setText}.
     * Otherwise, {@code link} is first wrapped in "<html><u>…</u></html>".
     *
     * @param link the link of the {@link JHyperlink}
     */
    public void setLink(String link) {
        if (link != null && !link.isEmpty() && !link.startsWith("<"))
            setText("<html><u>" + link + "</u></html>");
        else
            setText(link);
    }

    /**
     * Handles keyboard focus changes for the {@link JHyperlink}.
     */
    private class JHyperlinkFocusListener implements FocusListener {
        /**
         * Invoked when a component gains the keyboard focus.
         * @param e the {@link FocusEvent} to be processed
         */
        @Override
        public void focusGained(FocusEvent e) {
            JHyperlink.this.setBorder(_focusBorder);
        }

        /**
         * Invoked when a component loses the keyboard focus.
         * @param e the {@link FocusEvent} to be processed
         */
        @Override
        public void focusLost(FocusEvent e) {
            JHyperlink.this.setBorder(_emptyBorder);
        }
    }
}

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.