Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to get ellipsis in a JLabel containing HTML?

Tags:

java

swing

jlabel

When I combine HTML tags into the JLabel text I am loosing the ellipsis behavior that is shown when the space is too small to display the complete text. In my specific case, it is a TableCellRenderer which extends JLabel (swing's default or other). Now, when the column width is too small for the text to be shown fully, it is not showing the ellipsis.

See the image below for example: For the left column I wrapped the text at the renderer with HTML: setText("<html>" + "<strong>" + value.toString() + "</strong>" + "</html>");. As you can see when the column width is too small to contain the text, it is just cut. The right column however, showing the date and time and using DefaultTableCellRenderer is showing ellipsis when it fails to contain the complete text.

enter image description here

So my question is, can I have both? Meaning, wrapping the text with HTML and still get the ellipsis?

UPDATE:

I found the reason for not getting the ellipsis when using HTML. I followed the code from JComponent#paintComponent(Graphics g) all the way down to BasicLabelUI#layoutCL(...). See the following code snippet taken from the last. It is only clipping the string if it does not have the html property (which is true when the label text is wrapped with html). Yet I have no idea how to work around it:

    v = (c != null) ? (View) c.getClientProperty("html") : null;
    if (v != null) {
        textR.width = Math.min(availTextWidth,
                               (int) v.getPreferredSpan(View.X_AXIS));
        textR.height = (int) v.getPreferredSpan(View.Y_AXIS);
    } else {
        textR.width = SwingUtilities2.stringWidth(c, fm, text);
        lsb = SwingUtilities2.getLeftSideBearing(c, fm, text);
        if (lsb < 0) {
            // If lsb is negative, add it to the width and later
            // adjust the x location. This gives more space than is
            // actually needed.
            // This is done like this for two reasons:
            // 1. If we set the width to the actual bounds all
            //    callers would have to account for negative lsb
            //    (pref size calculations ONLY look at width of
            //    textR)
            // 2. You can do a drawString at the returned location
            //    and the text won't be clipped.
            textR.width -= lsb;
        }
        if (textR.width > availTextWidth) {
            text = SwingUtilities2.clipString(c, fm, text,
                                              availTextWidth);
            textR.width = SwingUtilities2.stringWidth(c, fm, text);
        }
        textR.height = fm.getHeight();
    }
like image 591
Assimiz Avatar asked Jun 30 '15 14:06

Assimiz


2 Answers

As long as the HTML content is simple, as in your question, the ellipsis showing can be accomplished with a custom made JLabel. Here is a working example. Just resize the window and you see the ellipsis appear and disappear and the text cut appropriately as the label resizes with the window.

import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import javax.swing.JLabel;
import javax.swing.border.Border;

public class SimpleHTMLJLabel extends JLabel
{
    private static final long serialVersionUID = -1799635451172963826L;

    private String textproper;
    private String ellipsis = "...";
    private int textproperwidth;
    private FontMetrics fontMetrics;
    private int ellipsisWidth;
    private int insetsHorizontal;
    private int borderHorizontal;

    public SimpleHTMLJLabel(String textstart, String textproper, String textend)
    {
        super(textstart + textproper + textend);
        this.textproper = textproper;
        insetsHorizontal = getInsets().left + getInsets().right;
        fontMetrics = getFontMetrics(getFont());
        calculateWidths();
        addComponentListener(new ComponentAdapter() {
            public void componentResized(ComponentEvent e)
            {
                int availablewidth = getWidth();
                if (textproperwidth > availablewidth - (insetsHorizontal + borderHorizontal)) 
                {
                    String clippedtextproper = textproper;
                    while (clippedtextproper.length() > 0 
                           && fontMetrics.stringWidth(clippedtextproper) + ellipsisWidth > availablewidth - (insetsHorizontal + borderHorizontal)) 
                    {
                        clippedtextproper = clipText(clippedtextproper);
                    }
                    setText(textstart + clippedtextproper + ellipsis + textend);
                } else 
                {
                    setText(textstart + textproper + textend);
                }
            }
        });
    }

    private void calculateWidths()
    {
        if (textproper != null) 
        {
            textproperwidth = fontMetrics.stringWidth(textproper);
        }

        if (ellipsis != null)
        {
            ellipsisWidth = fontMetrics.stringWidth(ellipsis);
        }
    }

    @Override
    public void setFont(Font font)
    {
        super.setFont(font);
        fontMetrics = getFontMetrics(getFont());
        calculateWidths();
    }

    private String clipText(String clippedtextproper)
    {
        return clippedtextproper.substring(0, clippedtextproper.length() - 1);
    }

    @Override
    public void setBorder(Border border)
    {
        super.setBorder(border);
        borderHorizontal = border.getBorderInsets(this).left + border.getBorderInsets(this).right;
    }
}

MAIN

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import javax.swing.BorderFactory;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;

public class Main
{
    public static void main(String[] args)
    {
        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run()
            {
                JFrame window = new JFrame();
                window.setResizable(true);
                window.setTitle("Label Test");
                window.getContentPane().add(getContent());
                window.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                window.setSize(400, 200);
                window.setLocationRelativeTo(null);
                window.setVisible(true);
            }
        });
    }

    protected static Component getContent()
    {
        JPanel panel = new JPanel(new BorderLayout());
        SimpleHTMLJLabel label = new SimpleHTMLJLabel("<html><strong>", "TEST1test2TEST3test4TEST5test6TEST7test8TEST", "</strong></html>");
        label.setBorder(BorderFactory.createCompoundBorder(BorderFactory.createLineBorder(Color.BLUE, 5),
        BorderFactory.createEmptyBorder(10, 10, 10, 10)));
        label.setFont(label.getFont().deriveFont(20F));
        panel.add(label, BorderLayout.CENTER);
        return panel;
    }
}
like image 183
gute Fee Avatar answered Oct 01 '22 18:10

gute Fee


I'm going to say: No, you can't have both.

I think if you want custom styling and ellipsis you will have to do it yourself without HTML and with a custom TableCellRenderer.

If you want to try and have your cake and eat it too, you might be able to get there by creating your own View object and setting it with c.putClientProperty("html", value) but I suspect that the HTML rendering code has no notion of ellipsing (text-overflow is an HTML 5ish feature) so you would have to figure out how to teach it to do that. I suspect this would be very difficult and much harder than just writing your own TableCellRenderer.

like image 36
Not Saying Avatar answered Oct 01 '22 18:10

Not Saying