2

I put a JTextPane into a JScrollPane. As I tried, the JTextPane will auto-wrap a long line if it exceed the width of the display area. And the auto wrapping is based on word boundary, such as a space character.

My content contains a lot of space. And I want to display it literally. So I need auto-wrapping, but I want it happen ONLY at the maximum width of display area, NOT on word boundary.

How?

What I have tried:

  • Replace all the space with '\0', so literally, my content is a single big word.

ADD 1

Below is my failed attempt after reading StanislavL's solution. I am not blaming his solution since my scenario is not exactly the same as his.

StanislavL's solution requires that a row contains at least 2 LabelViews. According to him, this is imposed by Swing's implementation of layout() method where forced break works only if row view has more than one child (see: http://java-sl.com/wrap.html). So StanislavL deliberately assigned a special attribute to the \r character which ensure a separate LabelView. And use the \r as a landmark of wrapping. But in my scenario I cannot insert any characters to my content.

My idea is simple, just provide a customized implementation of ViewFactory for the StyledEditorKit since the ViewFactory interface determines the break weight and how a break should happen:

this.jTextPane.setEditorKit(new StyledEditorKit(){
    @Override 
       public ViewFactory getViewFactory(){
           return new LetterWrappingStyledViewFactory(maxCharWidth);
        } 
    }); 

Below is my implementation of the interface ViewFactory:

public class LetterWrappingStyledViewFactory implements ViewFactory {


    public int maxCharWidth = -1; // this is the max width where I want wrap to happen.

    public LetterWrappingStyledViewFactory(int maxCharWidth) {
        this.maxCharWidth = maxCharWidth;
    }

    public View create(Element elem) {
        String kind = elem.getName();
        if (kind != null) {
            if (kind.equals(AbstractDocument.ContentElementName)) {
                return new LabelView(elem) {
                    public int getBreakWeight(int axis, float pos, float len) {
                        if (axis == View.X_AXIS) {
                            checkPainter();
                            int p0 = getStartOffset();
                            int p1 = getGlyphPainter().getBoundedPosition(this, p0, pos, len);
                            if (p1 > maxCharWidth)
                                return View.ForcedBreakWeight;
                            else
                                return View.BadBreakWeight;
                        }
                        return super.getBreakWeight(axis, pos, len);
                    }

                    public View breakView(int axis, int p0, float pos, float len) {
                        if (axis == View.X_AXIS) {
                            checkPainter();
                            int p1 = getGlyphPainter().getBoundedPosition(this, p0, pos, len);
                            if (p0 == getStartOffset() && p1 <= maxCharWidth) {
                                return this;
                            }
                            return createFragment(p0, maxCharWidth);
                        }
                        return this;
                    }
                };
            } 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);
    }
}
smwikipedia
  • 61,609
  • 92
  • 309
  • 482
  • 1
    Did you try [this](http://stackoverflow.com/questions/7036543/how-is-word-wrapping-implemented-in-jtextpane-and-how-do-i-make-it-wrap-a-strin) and [this](http://stackoverflow.com/questions/7156038/jtextpane-line-wrapping)? – user1803551 May 28 '15 at 08:59
  • Thanks. I have read both of them and I am trying to understand it. – smwikipedia May 28 '15 at 10:18
  • I'm not sure as to the details of what you want, but the premade tools by camickr and StanislavL are overall pretty good. If you find your answer in one of those links do close this as a duplicated. If not, elaborate. – user1803551 May 28 '15 at 19:05
  • `camickr`'s solution (https://tips4java.wordpress.com/2009/01/25/no-wrap-text-pane/) achieves a no-wrap JTextPane with proper caret visibility, which is not what I want. I need wrapping at display border rather than word boundary. – smwikipedia May 29 '15 at 06:39
  • Doesn't StanislavL's solution [here](http://java-sl.com/tip_html_letter_wrap.html) does it? – user1803551 May 29 '15 at 11:32
  • @user1803551 `StanislavL`'s solution achieves a letter-level wrap for `JEditorPane` with `HTMLEditorKit` or `PlainEditorKit`. To be specific, he customized the `HTMLEditorKit` by overriding the `HTMLFactory` which implements the `ViewFactory` interface. This solution doesn't work for me because I am working with `JTextPane` with a `StyledEditorKit` since I need to set style for the text. If I follow his way, I guess I need to override the `StyledViewFactory`, but unfortunately, it cannot be overridedn because it is an **internal** class `javax.swing.text.StyledEditorKit$StyledViewFactory` – smwikipedia Jun 01 '15 at 04:57
  • O.K., I'll try to work something out. – user1803551 Jun 01 '15 at 10:15
  • @user1803551 Thanks for your help. I updated my question. – smwikipedia Jun 01 '15 at 14:57
  • Well, you got the answer from the man himself :) – user1803551 Jun 02 '15 at 19:42
  • @user1803551 Yes, that's really an honor. ;) – smwikipedia Jun 03 '15 at 02:04

1 Answers1

4

This example works with StyledEditorKit (tested with java 7)

import javax.swing.*;
import javax.swing.text.*;


public class WrapApp extends JFrame {
    JEditorPane edit=new JEditorPane();
    public WrapApp() {
        super("Wrap in the mid");
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        edit.setEditorKit(new WrapEditorKit());
        edit.setText("111 222 333333333333333333333333333333333333333333333");

        getContentPane().add(new JScrollPane(edit));
        setSize(200,200);
        setLocationRelativeTo(null);
    }

    public static void main(String[] args) {
        WrapApp m = new WrapApp();
        m.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 int getBreakWeight(int axis, float pos, float len) {
        if (axis == View.X_AXIS) {
            checkPainter();
            int p0 = getStartOffset();
            int p1 = getGlyphPainter().getBoundedPosition(this, p0, pos, len);
            if (p1 == p0) {
                // can't even fit a single character
                return View.BadBreakWeight;
            }
            return View.GoodBreakWeight;
        }
        return super.getBreakWeight(axis, pos, len);
    }
    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);
        }
    }

    public View breakView(int axis, int p0, float pos, float len) {
        if (axis == View.X_AXIS) {
            checkPainter();
            int p1 = getGlyphPainter().getBoundedPosition(this, p0, pos, len);
            GlyphView v = (GlyphView) createFragment(p0, p1);
            return v;
        }
        return super.breakView(axis, p0, pos, len);
    }
}

UPDATE:

About the GlyphPainter. In fact we should find a position where we can break GlyphView (this past in getBoundedPosition()). p0 means char offset in the Document (GlyphView represents Character element or fragment of the Element)) SO imagine a big string of text. In the Document it's just one Element because it has all the same attributes. But we have to create multiple LabelView because the text fragment is too big to fit the available width. SO for the first label the p0 is 0. Then for the next one is an offset where we break our initial huge view. Then another offset if we break it once more. Then 2 parameters just representing x shift and widht of container (parent view for example).

SO we have a big text and should find where to break. Current way is easier because we don't care about priorities (spaces vs another chars).

StanislavL
  • 56,971
  • 9
  • 68
  • 98
  • Many thanks. It works like a charm for an initial test. I will do further test tomorrow. BTW, some more details about my scenario, I first use the `Document.insertString()` to fill the Document with spaces (kind of place holder). And then as character data arrived (actually it is some serial port data in VT100 protocol), insert character along with color attributes to certain location of the Document. – smwikipedia Jun 02 '15 at 14:17
  • I have read the javadoc about the `getBoundedPosition()` method. Unfortunately I didn't quite get it. Could you give some English-friendly elaboration? Especially the meaning of `p0, pos, len` parameter and how the `GlyphPainter` works. – smwikipedia Jun 02 '15 at 14:19
  • I will award the bonus credits once the S.O. site allows me to. ;) – smwikipedia Jun 02 '15 at 14:21