javaswingawtjtextareajdialog

JTextArea with line wrap reports incorrect preferred size to JDialog pack?


I am using multiple JTextAreas to display variable sized amounts of text, with LineWrap and WrapStyleWord both true. One application of this is displaying a JDialog with multiple of these text areas inside a JScrollPane. The panel that contains all of these text areas implements Scrollable (as explained here) allowing the JDialog to have a maximum size, but still shrink. The JDialog has a set horizontal size, however. The horizontal scroll bar is forced to never appear, and the vertical scrollbar should not appear unless the dialog reaches its maximum size. However, in cases where the JTextArea wraps its lines, the vertical scroll bar appears while the JDialog's height is smaller than its maximum. The issue seems to be that the JTextArea reports a preferred height of one row when the JDialog is packed, causing the height of the dialog to be smaller than the height of the panel as the text area takes up more than one row.

It seems to be a catch-22, as to get the proper height of the text area it needs to be added to a panel with the desired width to calculate line wrap, which would allow the actual lines to be counted (using a method found here). But the JDialog requires a preferred size before pack is called (from what I understand), causing the issue. To demonstrate the problem:

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Rectangle;
import javax.swing.BoxLayout;
import javax.swing.JDialog;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.Scrollable;

public class DisplayPopup extends JDialog {

    public DisplayPopup() {

        JPanel actual = new JPanel(); 
        actual.setLayout(new BoxLayout(actual, BoxLayout.Y_AXIS));
        actual.setOpaque(false);

        JTextArea jta = new JTextArea();
        jta.setText("Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed ac dictum magna. Nam sed mattis sem. Donec tortor felis, finibus in lectus fringilla, semper finibus turpis. ");
        jta.setFont(new java.awt.Font("Arial", 0, 18));
        jta.setForeground(new java.awt.Color(204, 204, 204));
        jta.setLineWrap(true);
        jta.setWrapStyleWord(true);
        jta.setEditable(false);
        jta.setBackground(new Color(45,45,45));
        jta.setHighlighter(null);
        actual.add(jta);

        PopupScroll ps = new PopupScroll(actual);

        JScrollPane scroll = new JScrollPane(); 
        scroll.getViewport().add(ps);

        setModalityType(java.awt.Dialog.ModalityType.MODELESS);
        setDefaultCloseOperation(javax.swing.WindowConstants.DISPOSE_ON_CLOSE);
        setResizable(false);
        setLocationRelativeTo(null);
        getContentPane().setLayout(new BorderLayout());
        getContentPane().add(scroll);

        pack();
    }

    public static void launch() {
        DisplayPopup dp = new DisplayPopup();
        dp.setVisible(true);
    }

    public static void main(String[] args) {                                                    
        javax.swing.SwingUtilities.invokeLater(DisplayPopup::launch);                                                                                       
    } 

    private class PopupScroll extends JPanel implements Scrollable {

        public PopupScroll(JPanel jp) {
            setBackground(Color.PINK);
            setLayout(new BorderLayout());
            add(jp);
        }

        @Override
        public Dimension getPreferredScrollableViewportSize() {
            return new Dimension(500,Math.min((int)getPreferredSize().getHeight(),700));
        }

        @Override
        public int getScrollableUnitIncrement(Rectangle arg0, int arg1, int arg2) {
            return 1;
        }

        @Override
        public int getScrollableBlockIncrement(Rectangle arg0, int arg1, int arg2) {
            return 1;
        }

        @Override
        public boolean getScrollableTracksViewportWidth() {
            return true; 
        }

        @Override
        public boolean getScrollableTracksViewportHeight() {
            return false;
        }

    }

}

The dialog does not size to include all lines of text. Unlike this solution to a similar issue, calling pack does not solve the problem. I have a few guesses about the solution, maybe using something besides a JTextArea (because I am only trying to display text...?), but I don't know what that would be. I've tried putting a hierarchy listener on the vertical scroll bar so I could detect when it is active without making the dialog visible, but that did not work. Is this a hard problem or am I missing something obvious?


Solution

  • Made a couple of changes and I see the text displayed on 3 lines:

    pack();
    pack();
    

    Yes that is correct I added a second pack(). First pack seems to recognize the width and the second pack causes the text to be wrapped.

    However, I also had to change:

    //actual.setLayout(new BoxLayout(actual, BoxLayout.Y_AXIS));
    actual.setLayout(new BorderLayout());
    

    I know you don't really want to use a BorderLayout (since you want multiple components aligned vertically), but somehow its preferred size calculation seems to work.

    Instead of the BoxLayout, you might be able to use a GridBagLayout. It allows you to specify the "fill" constraints. I would guess you would try with just the horizontal fill.

    Or another option is to use the Relative Layout. I would be easier than the GridBagLayout since you don't need to worry about any constraint. The code to use it would be:

    //actual.setLayout(new BoxLayout(actual, BoxLayout.Y_AXIS));
    RelativeLayout rl = new RelativeLayout(RelativeLayout.Y_AXIS);
    rl.setFill( true );
    actual.setLayout( rl );