2

I am trying to make a popup window that will contain just a single label. Ideally, I would specify a maximum width for the popup, and a string to display, and the popup would automatically size itself such that the text would wrap if it exceeds the specified max width, and it would be the correct height. Without complex usage of FontMetrics to brute-force through the calculations, is it possible to do such a thing in Swing? Here is an example of what I'm trying to do:

import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.FlowLayout;
import java.awt.Point;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.Popup;
import javax.swing.PopupFactory;
import javax.swing.SpringLayout;


public class PopupTest extends JFrame {
    private static final String FOX_MESSAGE = "<html>The lithe, quick, brown fox jumps " +
                                              "happily and steadily over the fat, slobbery, " +
                                              "lazy dogs</html>";
    public static void main(String[] args) {
        EventQueue.invokeLater(new Runnable() {
            public void run() {
                try {
                    PopupTest frame = new PopupTest();
                    frame.setVisible(true);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        });
    }

    public PopupTest() {
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        setBounds(100, 100, 450, 300);
        final JPanel contentPane = new JPanel();
        setContentPane(contentPane);
        contentPane.setLayout(new FlowLayout(FlowLayout.CENTER, 5, 5));

        final JButton btnLaunch = new JButton("Launch popup");
        btnLaunch.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent arg0) {
                Point btnLoc = btnLaunch.getLocationOnScreen();
                FoxPopup popup = new FoxPopup(FOX_MESSAGE, 200);
                popup.launch(contentPane, btnLoc.x, btnLoc.y + btnLaunch.getHeight());              
            }
        });
        contentPane.add(btnLaunch);
    }

    public class FoxPopup extends JPanel {
        private static final int ARBITRARILY_TALL = 9999;
        private Popup popup;

        public FoxPopup(String message, int width) {
            setBackground(Color.pink);
//          SpringLayout springLayout = new SpringLayout();
//          setLayout(springLayout);

            JLabel lblDescription = new JLabel(message);
//          springLayout.putConstraint(SpringLayout.NORTH, lblDescription, 5, SpringLayout.NORTH, this);
//          springLayout.putConstraint(SpringLayout.WEST, lblDescription, 5, SpringLayout.WEST, this);
//          springLayout.putConstraint(SpringLayout.EAST, lblDescription, -5, SpringLayout.EAST, this);
            add(lblDescription);
//          setPreferredSize(new Dimension(width, 65));
            setMaximumSize(new Dimension(width, ARBITRARILY_TALL));
        }

        public void launch(Component owner, int x, int y) {
            popup = PopupFactory.getSharedInstance().getPopup(owner, this, x, y);
            popup.show();
        }
    }
}

If I compile and run the above code, then I get a wide popup (wider than the 200 pixel max that I have requested) with all the text on a single line. If I uncomment all of the commented lines, then I get the desired behavior, but I must specify both the width and height in the setPreferredSize() call, and so it is not robust to handle different input messages.


Edit with solution based on Andrew Thompson's answer below

public class PopupTest extends JFrame {
    private static final String FOX_MESSAGE = "The lithe, quick, brown fox jumps " +
                                              "happily and steadily over the fat, slobbery, " +
                                              "lazy dogs";
    public static void main(String[] args) {
        EventQueue.invokeLater(new Runnable() {
            public void run() {
                try {
                    PopupTest frame = new PopupTest();
                    frame.setVisible(true);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        });
    }

    public PopupTest() {
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        setBounds(100, 100, 450, 300);
        final JPanel contentPane = new JPanel();
        setContentPane(contentPane);
        contentPane.setLayout(new FlowLayout(FlowLayout.CENTER, 5, 5));

        final JButton btnLaunch = new JButton("Launch popup");
        btnLaunch.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent arg0) {
                Point btnLoc = btnLaunch.getLocationOnScreen();
                FoxPopup popup = new FoxPopup(FOX_MESSAGE, 200);
                popup.launch(contentPane, btnLoc.x, btnLoc.y + btnLaunch.getHeight());              
            }
        });
        contentPane.add(btnLaunch);
    }

    public static class FoxPopup extends JPanel {
        private Popup popup;

        public FoxPopup(String message, int width) {
            setBackground(Color.pink);

            JLabel lblDescription = new JLabel(formatMessage(message, width));
            add(lblDescription);
        }

        private static String formatMessage(String message, int width) {
            return String.format("<html><body style='width: %d'>%s</body></html>", width, message);
        }

        public void launch(Component owner, int x, int y) {
            popup = PopupFactory.getSharedInstance().getPopup(owner, this, x, y);
            popup.show();
        }
    }
}
danBhentschel
  • 863
  • 7
  • 24

2 Answers2

4

See FixedWidthText which uses CSS to fix the width:

Community
  • 1
  • 1
Andrew Thompson
  • 168,117
  • 40
  • 217
  • 433
  • Excellent. That is exactly what I was hoping for. Thank you very much. I have added an example to my original question to show my implementation (for the uninitiated). – danBhentschel Aug 13 '14 at 14:20
0

I would probably use a word wrapping JTextArea rather than a JLabel with html, see the JMultiLineLabel answer here Java swing: Multiline labels?

As for the sizing of the Popup I don't know any other way other than to use FontMetrics although its not that complex.

public class FoxPopup extends JPanel {

    private Popup popup;

    public FoxPopup(String message, int width) {
        setBackground(Color.pink);
        setLayout(new BorderLayout());

        JTextArea textArea = createMultiLineLabel(message);

        add(textArea);

        setSize(textArea, width);
    }

    private JTextArea createMultiLineLabel(String message) {
        JTextArea textArea = new JTextArea(message);
        textArea.setEditable(false);
        textArea.setCursor(null);
        textArea.setOpaque(false);
        textArea.setFocusable(false);
        textArea.setFont(UIManager.getFont("Label.font"));
        textArea.setWrapStyleWord(true);
        textArea.setLineWrap(true);
        return textArea;
    }

    private void setSize(JTextArea textArea, int width) {
        FontMetrics fm = textArea.getFontMetrics(textArea.getFont());

        int textWidth = SwingUtilities.computeStringWidth(textArea.getText());

        Dimension d = new Dimension();
        d.setSize(width, fm.getHeight() * Math.ceil((double) textWidth / (double) width));

        setPreferredSize(d);
    }

    public void launch(Component owner, int x, int y) {
        popup = PopupFactory.getSharedInstance().getPopup(owner, this, x, y);
        popup.show();
    }
}
Community
  • 1
  • 1
Jamesy82
  • 369
  • 3
  • 10
  • Thank you for the response. It is useful information for me to have in the future, but I decided to go with Andrew Thompson's recommendation because I consider it to be slightly more elegant. – danBhentschel Aug 13 '14 at 14:21