16

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).

Community
  • 1
  • 1
Al-Khwarizmi
  • 453
  • 5
  • 8
  • Does using [`invokeLater()`](http://download.oracle.com/javase/tutorial/uiswing/concurrency/initial.html) help? – Catalina Island Dec 29 '11 at 14:36
  • I have exactly the same problem. Related: https://forums.oracle.com/forums/thread.jspa?threadID=2374090 (no answers...) The poster there already created a bug report, but it was closed as "not a bug", without a word of explanation... – PhiLho May 31 '12 at 11:21

4 Answers4

12

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);
            }
        }

    }
}
StanislavL
  • 55,186
  • 9
  • 58
  • 88
  • 1
    In fact all we need is to let getMinimumSpan() of LabelView return 0 for X_AXIS. ViewFactory is a way to replace default LabelView – StanislavL Nov 14 '12 at 09:36
  • aaach i can see and get it, thank you, quite correctly to works for `jtp.setComponentOrientation(RTL);` too – mKorbel Nov 14 '12 at 09:42
  • How are we suppose to solve the casting problem in the line jtp.setEditorKit(new WrapEditorKit());? I get a javax.swing.text.DefaultStyledDocument cannot be cast to javax.swing.text.html.HTMLDocument in that line. – Luis Gonçalves Jan 24 '13 at 08:11
  • You use another editor kit. Guess HTMLEditorKit. Do the same for the kit. – StanislavL Jan 27 '13 at 14:04
3

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.

Community
  • 1
  • 1
PhiLho
  • 38,673
  • 6
  • 89
  • 128
1

Take a look at this bug:

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

Dimitry
  • 4,284
  • 6
  • 24
  • 40
  • Thanks, it's remarkably similar... but unfortunately it doesn't seem to be the same bug (it has to do with attribute sets, and was fixed long ago) :/ – Al-Khwarizmi Dec 29 '11 at 16:42
  • Well, I think it is related, although the behavior in 1.6 wasn't changed. Look at the comment: "It should be noted that the requested default splitting behavior (breaking GlyphView at arbitrary place when no valid breakpoint is found by BreakIterator) is horribly wrong and should not be restored in any JDK release, either future or past." Looks like we need to do more work now... – PhiLho May 31 '12 at 11:24
1

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;
        }
Thomwiesel
  • 11
  • 1
  • Sorry: no doesn't work! this solves only the problem with long lines (containing white-spaces) - they are wrapped now correctly - but long words still didn't get wrapped :-( – Thomwiesel Jan 02 '12 at 19:10