Sunteți pe pagina 1din 3

"Forced line wrap" and "No wrap" in the JEditorPane http://java-sl.com/wrap.

html

Articles Projects Tips Downloads Contacts About


<Tip 19 of 19>
"Forced line wrap" and "No wrap" in the JEditorPane/JTextPane Loosing last empty
By Stanislav Lapitsky paragraph in HTML from
JEditorPane fix.
Unlike JTextArea wrapping related methods setLineWrap() and setWrapStyleWord() JEditorPane class JTextPane with HTMLEditorKit
doesn’t provide such features. The wrapping is defined by EditorKit implementation. By default paragraphs set ignores/removes empty last
wrap lines by word. Often programmer has to manually disable the wrapping or add a forced wrap. Forced wrap empty paragraphs when we try to
means paragraph remains the same but text is wrapped in the mid of row. The same behavior provide MS get html content via getText()
Word when user presses SHIFT+ENTER key. Another example is HTML tag <BR> which breaks line but doesn’t method. The behavior comes from
start a new paragraph. the protected boolean
inRange(Element next) method of
The article shows how to achieve this in JEditorPane. The solution is based on StyledEditorKit extension but the HTMLWriter. Read more
can be implemented in any other kit. about loosing last paragraph in
JEditorPane fix>
"No wrap" implementation.
Normally line wrap happens when row content’s width is bigger than available space. The available space is
defined as width of paragraph’s container. The container sets size of paragraph and performs layout. The first
step is overriding layout and passing max width. This way we set the available width to be much bigger that
necessary. So the paragraph will have one row and no need to wrap. We override ParagraphView class with
own implementation of layout().

public void layout(int width, int height) {


super.layout(Short.MAX_VALUE, height);
}

Then we correct minimum paragraph’s span calculation to provide correct feedback to container and let
container get real span. This fixes some issues when JEditorPane is added into JScrollPane.

public float getMinimumSpan(int axis) {


return super.getPreferredSpan(axis);
}

The result is a paragraph with no wrap. If we need to switch warp on/off we can define a flag and call
super.layout() or custom layout depending on what we need – wrap/no wrap.

Line breaking algorithm.


To understand how the wrapping works let’s describe line breaking algorithm of paragraph.

LabelView class represents separate fragment of text with the same attributes. ParagraphView layout
LabelViews in rows breaking labels if necessary. First step of layout is creation of single very long line (pool
of LabelViews) which will be continuously broken to fit available paragraph space.

Paragraph creates a row and adds to the row the first LabelView from the pool and measure current row. If
the width of row after adding the label is less than available paragraph width the next label is added till the
row width exceeds the available width. As soon as the width is exceeded paragraph starts to search where the
row can be broken. It means paragraph need an offset where we can break view. All the labels in the row are
asked for break weight. A quote from javadoc: “<break weight > - Determines how attractive a break
opportunity in this view is. This can be used for determining which view is the most attractive to call breakView
method on in the process of formatting. The higher the weight, the more attractive the break. A value equal to
or lower than View.BadBreakWeight should not be considered for a break. A value greater than or equal to
View.ForcedBreakWeight should be broken.”

There are four base break weights the LabelView can return:
View.BadBreakWeight – means no break possible.
View.GoodBreakWeight – can break if there is no better opportunity.
View.ExcellentBreakWeight – it’s preferred to break the view.
View.ForcedBreakWeight – the view must be broken.

In most cases LabelViews returns View.ExcellentBreakWeight if there is a space chars ‘ ‘, ‘\t’ etc. (Word
wrap supposes to break views by spaces) and View.GoodBreakWeight if there is no space but only letters. To
explain this imagine following. You type a very long string of characters and ask for break weight. If there is a
space we would prefer to break by space and start a new word on a new row. If there is no space we can break
in any possible place to fit our row in available paragraph width.

Forced wrap implementation.


We define a special character ‘\r’ in model. The character means we want to break row after the char. The char
is inserted with a special attribute to provide a separate view for the char. This is done because of swing’s
implementation of layout where forced break works only if row view has more than one child. (See
FlowView.FlowStrategy inner class line #477 in swing’s source code). The attribute can be used with the ‘\r’
only and must be removed from input Attributes of the kit we use.

1 of 3 3/9/2011 12:04 AM
"Forced line wrap" and "No wrap" in the JEditorPane http://java-sl.com/wrap.html

We also associate line break insertion with SHIFT+ENTER key combination. The code looks like this:

protected void insertLineBreak() {


try {
int offs = edit.getCaretPosition();
Document doc = edit.getDocument();
SimpleAttributeSet attrs;
if (doc instanceof StyledDocument) {
attrs = new SimpleAttributeSet(((StyledDocument)doc).getCharacterElement(offs).getAttributes());
}
else {
attrs = new SimpleAttributeSet();
}
attrs.addAttribute(LINE_BREAK_ATTRIBUTE_NAME,Boolean.TRUE);
doc.insertString(offs, "\r", attrs);
edit.setCaretPosition(offs+1);
}
catch (BadLocationException ex) {
//should never happens
ex.printStackTrace();
}
}

protected void initKeyMap() {


Keymap kMap=edit.getKeymap();
Action a=new AbstractAction() {
public void actionPerformed(ActionEvent e) {
insertLineBreak();
}
};
kMap.addActionForKeyStroke(KeyStroke.getKeyStroke(KeyEvent.VK_ENTER,KeyEvent.SHIFT_MASK),a);
}

Then we extend LabelView and provide proper break weight calculation.

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;
}
try {
//if the view contains line break char return forced break
if (getDocument().getText(p0, p1 - p0).indexOf("\r") >= 0) {
return View.ForcedBreakWeight;
}
}
catch (BadLocationException ex) {
//should never happen
}
}
return super.getBreakWeight(axis, pos, len);
}

and change code to break the label by the ‘\r’ char. Like this:

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);
try {
//if the view contains line break char break the view
int index = getDocument().getText(p0, p1 - p0).indexOf("\r");
if (index >= 0) {
GlyphView v = (GlyphView) createFragment(p0, p0 + index + 1);
return v;
}
}
catch (BadLocationException ex) {
//should never happen
}
}
return super.breakView(axis, p0, pos, len);
}

The final step is extending kit. All we need it to replace SUN’s views with own implementation and remove the

2 of 3 3/9/2011 12:04 AM
"Forced line wrap" and "No wrap" in the JEditorPane http://java-sl.com/wrap.html

custom attribute from input attributes.

class WrapEditorKit extends StyledEditorKit {


ViewFactory defaultFactory=new WrapColumnFactory();
public ViewFactory getViewFactory() {
return defaultFactory;
}

public MutableAttributeSet getInputAttributes() {


MutableAttributeSet mAttrs=super.getInputAttributes();
mAttrs.removeAttribute(WrapApp.LINE_BREAK_ATTRIBUTE_NAME);
return mAttrs;
}
}

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

Appendix

Here is the full source code of the wrap example.

Back to Table of Content

Copyright © 2003-2010 Stanislav Lapitsky. All Rights Reserved.

3 of 3 3/9/2011 12:04 AM

S-ar putea să vă placă și