Wrap long words in JTextPane (Java 7)

In all versions of Java up to 6, the default behaviour of a JTextPane put inside a JScrollPane was: wrap lines at word boundaries if possible. If not, then wrap them anyway.

In JDK 7, the default behaviour seems to be: wrap lines at word boundaries if possible. If not, just expand the width of the JTextPane (never wrap long words).

It is easy to reproduce this, here is a SSCCE:

public class WrappingTest extends JFrame
{

    public static void main ( String[] args )
    {
        new WrappingTest(); 
    }

    public WrappingTest ()
    {
        setSize(200,200);
        getContentPane().setLayout(new BorderLayout());
        JTextPane jtp = new JTextPane();
        JScrollPane jsp = new JScrollPane(jtp);
        jsp.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED);
        getContentPane().add(jsp,BorderLayout.CENTER);
        setVisible(true);
    }

}

Just run it in JDK 6 and in JDK 7, write some small words, and write a long word, and you will see the difference.

My question is simple... the new default behaviour in JDK 7 totally messes a program of mine (they should be more careful at Oracle with changing this kind of defaults... they seem unimportant but when you're using a JTextPane to display data that usually contains very long strings of letters, they're not so unimportant - in fact I'm going to file a bug report, but I'd like to have a workaround while/if they don't resolve it). Any way to go back to the previous behaviour?

Note that I have checked the answer to the related question How is word-wrapping implemented in JTextPane, and how do I make it wrap a string without spaces? but it doesn't answer this question - it provides a way of making the JTextPane wrap without any regard at all for whitespace, but for me the desired behaviour is split lines at whitespace if possible, and elsewhere if not possible (as in previous Java versions).

Answers


For me the fix works (tested under 1.7.0_09)

import javax.swing.*;
import javax.swing.text.*;
import java.awt.*;

public class WrapTestApp extends JFrame {

    public static void main ( String[] args ) {
        new WrapTestApp();
    }

    public WrapTestApp () {
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        setSize(200,200);
        getContentPane().setLayout(new BorderLayout());
        JTextPane jtp = new JTextPane();
        jtp.setEditorKit(new WrapEditorKit());
        JScrollPane jsp = new JScrollPane(jtp);
        jsp.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED);
        getContentPane().add(jsp, BorderLayout.CENTER);
        jtp.setText("ExampleOfTheWrapLongWordWithoutSpaces");
        setVisible(true);
    }

    class WrapEditorKit extends StyledEditorKit {
        ViewFactory defaultFactory=new WrapColumnFactory();
        public ViewFactory getViewFactory() {
            return defaultFactory;
        }

    }

    class WrapColumnFactory implements ViewFactory {
        public View create(Element elem) {
            String kind = elem.getName();
            if (kind != null) {
                if (kind.equals(AbstractDocument.ContentElementName)) {
                    return new WrapLabelView(elem);
                } else if (kind.equals(AbstractDocument.ParagraphElementName)) {
                    return new ParagraphView(elem);
                } else if (kind.equals(AbstractDocument.SectionElementName)) {
                    return new BoxView(elem, View.Y_AXIS);
                } else if (kind.equals(StyleConstants.ComponentElementName)) {
                    return new ComponentView(elem);
                } else if (kind.equals(StyleConstants.IconElementName)) {
                    return new IconView(elem);
                }
            }

            // default to text display
            return new LabelView(elem);
        }
    }

    class WrapLabelView extends LabelView {
        public WrapLabelView(Element elem) {
            super(elem);
        }

        public float getMinimumSpan(int axis) {
            switch (axis) {
                case View.X_AXIS:
                    return 0;
                case View.Y_AXIS:
                    return super.getMinimumSpan(axis);
                default:
                    throw new IllegalArgumentException("Invalid axis: " + axis);
            }
        }

    }
}

Good catch from @dk89, but alas the given workarounds don't work: JDK 7 apparently still doesn't offer a wait to set a custom BreakIterator on a JTextComponent; not even on a GlyphView, where the generation of the BreakIterator is private. And if we insert the string char by char, it still doesn't work: I suppose the consecutive runs of text with identical style (AttributeSet) are collapsed together.

I have spent two days trying to do a custom EditorKit, as advised elsewhere, but it doesn't work well (with JDK 1.7.0_4 at least) as the text.

I tried the solution given at How to word wrap text stored in JTextPanes which are cells in a JList and a variant found at http://www.experts-exchange.com/Programming/Languages/Java/Q_20393892.html

But I found out that the breakView is no longer called when the JTextPane is smaller than the longest word in the sentence. So it doesn't work at all when there is only one (long) word. That's our case, as we display user-provided, identifier-like strings, often without spaces, in rather small spaces.

I finally found a simple solution, derived from the suggestion in the bug report: indeed, insert the string char by char, but alternate styles! Thus, we have as many segments as we have chars, and the string is wrapped at char bounds. Until the next "bug fix"?

Code snippets:

private JTextPane tp;
private SimpleAttributeSet sas = new SimpleAttributeSet();

tp= new JTextPane();
sas.addAttribute( "A", "C" ); // Arbitrary attribute names and value, not used actually

    // Set the global attributes (italics, etc.)
    tp.setParagraphAttributes(styleParagraphAttributes, true);

    Document doc = tp.getDocument();
    try
    {
        doc.remove(0, doc.getLength()); // Clear
        for (int i = 0; i < textToDisplay.length(); i++)
        {
            doc.insertString(doc.getLength(), textToDisplay.substring(i, i+1),
                    // Change attribute every other char
                    i % 2 == 0 ? null : sas);
        }
    }
    catch (BadLocationException ble)
    {
        log.warn("Cannot happen...", ble);
    }

As stated in the bug, they should have provided an easy way (some property perhaps, or some injectable stuff) to revert to the old behavior.


Take a look at this bug:

http://bugs.sun.com/view_bug.do?bug_id=6539700


Hi I've had the same problem but found a work-around:

just create an extended class of JTextPane e.g.

        MyWrapJTextPane extends JTextPane

and overwrite the following method - it works ;-)

        public boolean getScrollableTracksViewportWidth() {
            return true;
        }

Need Your Help

MissingSourceFile: cannot load such file -- net/scp

ruby scp net-ssh

Since upgrading my Rails (2.3.17) app to Ruby 1.9.3 I lost the ssh.sftp.upload! method, so I'm trying to make use of the net-scp library, however I can't seem to use it in Rails. In irb I can howe...