JEditorPane line in Java7

First of all, I hope this is not a problem, I started a new topic. Tbh I have no clue how to ask a question based on an answer already, so I did it.

I am new to Java and my problem is as follows. I am writing a small chat program, and I use JEditorPane with HTMLEditorKit to display text in different colors, to display emoticons and display hyperlinks.

My problem, and after some research, I found out that the problem may be related to Java7, I can not get linewrap to work properly. I want the text wrapped in a word and wrapped in the middle of lines that are larger than the width of the component. The word wrap works fine, but if someone enters a fairly long line, JEditorPane expands and you need to resize the frame to get everything on the screen, which I don't want.

I tried several fixes for this problem, but they only allow message wrapping, so word wrapping no longer works. In addition, I want the user to be able to complete his text by pressing Enter. To do this, I add \ n to the text and with corrections, this will no longer affect the result, and everything will be displayed on one line.

It seems to me that I spent years on the Internet to find a solution, but unitl now didn’t work for my case, especially since it was the same fix all the time. Hope you guys can help me.

It means:

What I have:

  • A line wraps a word in the case of long lines separated by spaces.
  • if you use Windows and your input contains lines created by pressing the enter button, they will also wrap
  • If you enter a very long line without spaces, the panel expands and you need to resize the frame
  • HTML formatting allows me to display different colors, as well as hyperlinks and emoticons.

What I need:

  • Work with word wrap, as at the moment, if possible, but wrap letters ONLY in case of long lines, not separated by spaces, to prevent the panel from expanding.
  • Manually added streams of lines made by pressing ENTER in the input area or if I copy pre-formatted text to the input panel
  • HTML formatting as if I already

What I tried and what did not help:

jtextpane does not wrap text and JTextPane does not wrap text

Here is what code you can try yourself. In the lower left corner is the input area for entering some text. You can also add line wrappers by pressing the enter button. After clicking the button, you will see the text in the area above.

 import java.awt.BorderLayout; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.KeyEvent; import java.awt.event.KeyListener; import java.io.IOException; import javax.swing.BorderFactory; import javax.swing.JButton; import javax.swing.JEditorPane; import javax.swing.JFrame; import javax.swing.JPanel; import javax.swing.JTextArea; import javax.swing.border.TitledBorder; import javax.swing.text.BadLocationException; import javax.swing.text.html.HTMLDocument; import javax.swing.text.html.HTMLEditorKit; import javax.swing.text.html.StyleSheet; @SuppressWarnings("serial") public class LineWrapTest extends JFrame implements ActionListener, KeyListener { private JButton btnSend; private JTextArea textAreaIn; private JEditorPane textAreaOut; private HTMLEditorKit kit; private HTMLDocument doc; public LineWrapTest() { this.setSize(600, 500); this.setDefaultCloseOperation(EXIT_ON_CLOSE); this.setLocationRelativeTo(null); this.setTitle("Linewrap Test"); } /** * Not important for problem */ public void paintScreen() { this.setLayout(new BorderLayout()); this.add(this.getPanelOut(), BorderLayout.CENTER); this.add(this.getPanelIn(), BorderLayout.SOUTH); this.textAreaIn.requestFocusInWindow(); this.setVisible(true); } /** * Not important for problem * * @return panelOut */ private JPanel getPanelOut() { JPanel panelOut = new JPanel(); panelOut.setLayout(new BorderLayout()); this.textAreaOut = new JEditorPane(); this.textAreaOut.setEditable(false); this.textAreaOut.setContentType("text/html"); this.kit = new HTMLEditorKit(); this.doc = new HTMLDocument(); StyleSheet styleSheet = this.kit.getStyleSheet(); this.kit.setStyleSheet(styleSheet); this.textAreaOut.setEditorKit(this.kit); this.textAreaOut.setDocument(this.doc); TitledBorder border = BorderFactory.createTitledBorder("Output"); border.setTitleJustification(TitledBorder.CENTER); panelOut.setBorder(border); panelOut.add(this.textAreaOut); return panelOut; } /** * Not important for problem * * @return panelIn */ private JPanel getPanelIn() { JPanel panelIn = new JPanel(); panelIn.setLayout(new BorderLayout()); this.textAreaIn = new JTextArea(); this.textAreaIn.setLineWrap(true); this.textAreaIn.setWrapStyleWord(true); TitledBorder border = BorderFactory.createTitledBorder("Input"); border.setTitleJustification(TitledBorder.CENTER); panelIn.setBorder(border); panelIn.add(this.getBtnSend(), BorderLayout.EAST); panelIn.add(this.textAreaIn, BorderLayout.CENTER); return panelIn; } /** * Not important for problem * * @return btnSend */ private JButton getBtnSend() { this.btnSend = new JButton("Send"); this.btnSend.addActionListener(this); return this.btnSend; } private void append(String text) { try { this.kit.insertHTML(this.doc, this.doc.getLength(), text, 0, 0, null); } catch (BadLocationException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } } private String getHTMLText() { String txtIn = this.textAreaIn.getText().trim().replaceAll(SEPARATOR, "<br/>"); StringBuffer htmlBuilder = new StringBuffer(); htmlBuilder.append("<HTML>"); htmlBuilder.append(txtIn); htmlBuilder.append("</HTML>"); return htmlBuilder.toString(); } @Override public void actionPerformed(ActionEvent e) { if (e.getSource() == this.btnSend) { this.append(this.getHTMLText()); this.textAreaIn.setText(""); this.textAreaIn.requestFocusInWindow(); } } public static void main(String[] args) { LineWrapTest test = new LineWrapTest(); test.paintScreen(); } @Override public void keyPressed(KeyEvent e) { if (e.getKeyCode() == KeyEvent.VK_ENTER) if (!this.textAreaIn.getText().trim().isEmpty()) this.textAreaIn.setText(this.textAreaIn.getText() + SEPARATOR); } @Override public void keyReleased(KeyEvent e) { } @Override public void keyTyped(KeyEvent e) { } } 

UPDATE: base on some parts of http://java-sl.com/tip_java7_text_wrapping_bug_fix.html

Somehow I thought I needed to get a little closer to my goal. I tried to combine the hotfix for HTMLEditorKit with the StlyedEditorKit hotfix. But I have to be honest, I don’t know what I actually did there :( The sad thing is that the manual wraping line no longer works with this as a replacement for HTMLEditorKit. Perhaps you can use this as the basis for a better implementation.

To use it in my example, just create a new class in the project using CustomEditorKit and replace the HTMLEditorKit in the example with this CustomEditorKit. You will notice that word and letter processing works now, but if you press ENTER to get your own line, this change will no longer be displayed on the output panel, and everything will be displayed on one line. Another strange problem is that if you change the frame size, the lines will sometimes be on top of each other.

 import javax.swing.SizeRequirements; import javax.swing.text.Element; import javax.swing.text.View; import javax.swing.text.ViewFactory; import javax.swing.text.html.HTMLEditorKit; import javax.swing.text.html.InlineView; import javax.swing.text.html.ParagraphView; @SuppressWarnings("serial") public class CustomEditorKit extends HTMLEditorKit { @Override public ViewFactory getViewFactory() { return new HTMLFactory() { @Override public View create(Element e) { View v = super.create(e); if (v instanceof InlineView) { return new InlineView(e) { @Override public int getBreakWeight(int axis, float pos, float len) { return GoodBreakWeight; } @Override public View breakView(int axis, int p0, float pos, float len) { if (axis == View.X_AXIS) { this.checkPainter(); this.removeUpdate(null, null, null); } return super.breakView(axis, p0, pos, len); } }; } else if (v instanceof ParagraphView) { return new ParagraphView(e) { @Override protected SizeRequirements calculateMinorAxisRequirements(int axis, SizeRequirements r) { if (r == null) { r = new SizeRequirements(); } float pref = this.layoutPool.getPreferredSpan(axis); float min = this.layoutPool.getMinimumSpan(axis); // Don't include insets, Box.getXXXSpan will include them. r.minimum = (int) min; r.preferred = Math.max(r.minimum, (int) pref); r.maximum = Integer.MAX_VALUE; r.alignment = 0.5f; return r; } }; } return v; } }; } } 
+4
source share
2 answers

OK! So, I finally got everything that you had problems with work. It required some research and a lot of trial and error, but here it is:

Here is what I did:

  • Put JEditorPane in JScrollPane so you can scroll up and down as the message grows
  • Added custom word wrap. Custom word wrapping will wrap words and long words in the right place on the word. You were right, this is a bug with the current version of Java. http://bugs.sun.com/view_bug.do?bug_id=7125737
  • Added the ability to transfer the user to a new line by pressing Enter. This interfered with the personalized word wrap, so you may not like how I achieved it. In the sample code, I suggest other options.
  • Maintain your HTMLDocument abilities. I was tempted not to do this, but I found a job around so that it could be saved.
  • The app still uses JEditorPane, but you can switch it to JTextPane if you want. I tried both and they were both functional.

So here is the code. It is a little longer and you can change it based on your preference. I commented on where I made the changes and tried to explain them.

 import java.awt.BorderLayout; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.KeyEvent; import java.awt.event.KeyListener; import java.io.IOException; import javax.swing.BorderFactory; import javax.swing.JButton; import javax.swing.JEditorPane; import javax.swing.JFrame; import javax.swing.JPanel; import javax.swing.JScrollPane; import javax.swing.JTextArea; import javax.swing.KeyStroke; import javax.swing.SizeRequirements; import javax.swing.border.TitledBorder; import javax.swing.text.*; import javax.swing.text.html.HTMLDocument; import javax.swing.text.html.HTMLEditorKit; import javax.swing.text.html.InlineView; import javax.swing.text.html.StyleSheet; @SuppressWarnings("serial") public class LineWrapTest extends JFrame implements ActionListener, KeyListener { //This is the separator. private String SEPARATOR = System.getProperty("line.separator"); private JButton btnSend; private JTextArea textAreaIn; private JEditorPane textAreaOut; private JScrollPane outputScrollPane; private HTMLEditorKit kit; private HTMLDocument doc; public LineWrapTest() { this.setSize(600, 500); this.setDefaultCloseOperation(EXIT_ON_CLOSE); this.setLocationRelativeTo(null); this.setTitle("Linewrap Test"); } /** * Not important for problem */ public void paintScreen() { this.setLayout(new BorderLayout()); this.add(this.getPanelOut(), BorderLayout.CENTER); this.add(this.getPanelIn(), BorderLayout.SOUTH); this.textAreaIn.requestFocusInWindow(); this.setVisible(true); } /** * Not important for problem * * @return panelOut */ private JPanel getPanelOut() { JPanel panelOut = new JPanel(); panelOut.setLayout(new BorderLayout()); this.textAreaOut = new JEditorPane(); this.textAreaOut.setEditable(false); this.textAreaOut.setContentType("text/html"); //I added this scroll pane. this.outputScrollPane = new JScrollPane(this.textAreaOut); /* * This is a whole whack of code. It a combination of two sources. * It achieves the wrapping you desire: by word and longgg strings * It is a custom addition to HTMLEditorKit */ this.kit = new HTMLEditorKit(){ @Override public ViewFactory getViewFactory(){ return new HTMLFactory(){ public View create(Element e){ View v = super.create(e); if(v instanceof InlineView){ return new InlineView(e){ public int getBreakWeight(int axis, float pos, float len) { //return GoodBreakWeight; 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(SEPARATOR) >= 0) { return View.ForcedBreakWeight; } } catch (BadLocationException ex) { //should never happen } } 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); try { //if the view contains line break char break the view int index = getDocument().getText(p0, p1 - p0).indexOf(SEPARATOR); 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); } }; } else if (v instanceof ParagraphView) { return new ParagraphView(e) { protected SizeRequirements calculateMinorAxisRequirements(int axis, SizeRequirements r) { if (r == null) { r = new SizeRequirements(); } float pref = layoutPool.getPreferredSpan(axis); float min = layoutPool.getMinimumSpan(axis); // Don't include insets, Box.getXXXSpan will include them. r.minimum = (int)min; r.preferred = Math.max(r.minimum, (int) pref); r.maximum = Integer.MAX_VALUE; r.alignment = 0.5f; return r; } }; } return v; } }; } }; this.doc = new HTMLDocument(); StyleSheet styleSheet = this.kit.getStyleSheet(); this.kit.setStyleSheet(styleSheet); this.textAreaOut.setEditorKit(this.kit); this.textAreaOut.setDocument(this.doc); TitledBorder border = BorderFactory.createTitledBorder("Output"); border.setTitleJustification(TitledBorder.CENTER); panelOut.setBorder(border); //I changed this to add the scrollpane, which now contains //the JEditorPane panelOut.add(this.outputScrollPane); return panelOut; } /** * Not important for problem * * @return panelIn */ private JPanel getPanelIn() { JPanel panelIn = new JPanel(); panelIn.setLayout(new BorderLayout()); this.textAreaIn = new JTextArea(); this.textAreaIn.setLineWrap(true); this.textAreaIn.setWrapStyleWord(true); //This disables enter from going to a new line. Your key listener does that. this.textAreaIn.getInputMap().put(KeyStroke.getKeyStroke("ENTER"), "none"); //For the key listener to work, it needs to be added to the component this.textAreaIn.addKeyListener(this); TitledBorder border = BorderFactory.createTitledBorder("Input"); border.setTitleJustification(TitledBorder.CENTER); panelIn.setBorder(border); panelIn.add(this.getBtnSend(), BorderLayout.EAST); panelIn.add(this.textAreaIn, BorderLayout.CENTER); return panelIn; } /** * Not important for problem * * @return btnSend */ private JButton getBtnSend() { this.btnSend = new JButton("Send"); this.btnSend.addActionListener(this); return this.btnSend; } private void append(String text) { try { this.kit.insertHTML(this.doc, this.doc.getLength(), text, 0, 0, null); } catch (BadLocationException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } } private String getHTMLText() { //I tried to find a work around for this but I couldn't. It could be done //by manipulating the HTMLDocument but it beyond me. Notice I changed //<br/> to <p/>. For some reason, <br/> no longer went to the next line //when I added the custom wrap. <p/> seems to work though. String txtIn = this.textAreaIn.getText().trim().replaceAll(SEPARATOR, "<p/>"); //My IDE recommends you use StringBuilder instead, that up to you. //I am not sure what the difference would be. StringBuffer htmlBuilder = new StringBuffer(); htmlBuilder.append("<HTML>"); htmlBuilder.append(txtIn); htmlBuilder.append("</HTML>"); return htmlBuilder.toString(); } @Override public void actionPerformed(ActionEvent e) { if (e.getSource() == this.btnSend) { this.append(this.getHTMLText()); this.textAreaIn.setText(""); this.textAreaIn.requestFocusInWindow(); } } public static void main(String[] args) { LineWrapTest test = new LineWrapTest(); test.paintScreen(); } @Override public void keyPressed(KeyEvent e) { if (e.getKeyCode() == KeyEvent.VK_ENTER){ if (!this.textAreaIn.getText().trim().isEmpty()) { //I made this work by defining the SEPARATOR. //You could use append(Separator) instead if you want. this.textAreaIn.setText(this.textAreaIn.getText() + SEPARATOR); } } } @Override public void keyReleased(KeyEvent e) { } @Override public void keyTyped(KeyEvent e) { } } 

Here are the (most) links that I used to solve this problem:

Enabling Word Wrapping in JTextPane with HTMLDocument

A custom migration is a combination of these two:

http://java-sl.com/tip_html_letter_wrap.html

http://java-sl.com/wrap.html

Removing a binding for JTextArea:

http://docs.oracle.com/javase/tutorial/uiswing/misc/keybinding.html

If you have any questions, just write below. I will answer them. I sincerely hope this solves your problems.

+5
source

The deadliest best solution I found: <br> correctly handled by HTMLEditorKit , but Patrick Sebastien's post mentions that this is not the case. This is because its ViewFactory threatens the entire InlineView object as wrappable, but BRView also an InlineView . See My solution below:

 class WrapColumnFactory extends HTMLEditorKit.HTMLFactory { @Override public View create(Element elem) { View v = super.create(elem); if (v instanceof LabelView) { // the javax.swing.text.html.BRView (representing <br> tag) is a LabelView but must not be handled // by a WrapLabelView. As BRView is private, check the html tag from elem attribute Object o = elem.getAttributes().getAttribute(StyleConstants.NameAttribute); if ((o instanceof HTML.Tag) && o == HTML.Tag.BR) { return v; } return new WrapLabelView(elem); } return v; } } class WrapLabelView extends LabelView { public WrapLabelView(Element elem) { super(elem); } @Override 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); } } } 
+1
source

Source: https://habr.com/ru/post/1490350/


All Articles