Link to home
Start Free TrialLog in
Avatar of rzvika2
rzvika2

asked on

word wrap in DefaultStyledDocument

Hi!
I'm using DefaultStyledDocument (new DefaultStyledDocument(new StyleContext());)
I want that the text will be word wrapped.
Can you tell me how to do it?
Thanks!
Avatar of TimYates
TimYates
Flag of United Kingdom of Great Britain and Northern Ireland image

Assuming you're putting it in a JTextArea,

jTextArea.setWrapStyleWord( true ) ;

should do it...
Avatar of rzvika2
rzvika2

ASKER

I'm using JTextPane.
I want to insert strings with colors (that's why)
Errr....this does wordWrapping:

--------------------------------------------

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

public class WordWrap extends Frame
{
  BorderLayout borderLayout1 = new BorderLayout();
  JScrollPane scroll = new JScrollPane();
  JTextPane textPane = new JTextPane();
  public WordWrap()
  {
    try
    {
      jbInit();
    }
    catch(Exception e)
    {
      e.printStackTrace();
    }
  }

  private void jbInit() throws Exception
  {
    this.setLayout(borderLayout1);
    textPane.setDocument( new DefaultStyledDocument( new StyleContext() ) );
    textPane.setText("This is a quick and dirty test to see if we can do word wrapping on a JTextPane");
    this.addWindowListener(new java.awt.event.WindowAdapter()
    {
      public void windowClosing(WindowEvent e)
      {
        this_windowClosing(e);
      }
    });
    this.add(scroll, BorderLayout.CENTER);
    scroll.getViewport().add( textPane, null );
    scroll.setPreferredSize( new Dimension( 300, 300 ) );
  }

  void this_windowClosing(WindowEvent e)
  {
    System.exit( 0 ) ;
  }

  public static void main(String[] args)
  {
    WordWrap wordWrap = new WordWrap();
    wordWrap.pack() ;
    wordWrap.show();
  }
}
Avatar of rzvika2

ASKER

****.
This is the opposit that  I've wanted.
by default it is word wrapped, but I don't want it to be like this.
I'm so sorry, you will get the points.
Can you tell me how to make the word not wrapped?
Thanks!
Ahhhhh! ;-)

hehehehe

Yeah, extend JTextPane

JTextPane extends JEditorPane which implements Scrollable

So what you need to do, is override the part of the Scrollable interface that checks if the JTextPane should fix itself to the width of the ScrollPane...

Hope you see what I mean...anyway...here's the code ;-)

Tim

--------------------------

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

public class WordWrap extends Frame
{
  class MyTextPane extends JTextPane
  {
    public boolean getScrollableTracksViewportWidth()
    {
      return false ;
    }
  }

  BorderLayout borderLayout1 = new BorderLayout();
  JScrollPane scroll = new JScrollPane();
  MyTextPane textPane = new MyTextPane();
  public WordWrap()
  {
    try
    {
      jbInit();
    }
    catch(Exception e)
    {
      e.printStackTrace();
    }
  }

  private void jbInit() throws Exception
  {
    this.setLayout(borderLayout1);
    StyleContext sc = new StyleContext() ;
    DefaultStyledDocument dsd = new DefaultStyledDocument( sc ) ;
    textPane.setStyledDocument( dsd );
    Style s = sc.getStyle( "default" ) ;
    Enumeration e = s.getAttributeNames() ;
    while( e.hasMoreElements() )
    {
      System.out.println( e.nextElement() ) ;
    }
    textPane.setText("This is a quick and dirty test to see if we can do word wrapping on a JTextPane");
    this.addWindowListener(new java.awt.event.WindowAdapter()
    {
      public void windowClosing(WindowEvent e)
      {
        this_windowClosing(e);
      }
    });
    this.add(scroll, BorderLayout.CENTER);
    scroll.getViewport().add( textPane, null );
    scroll.setPreferredSize( new Dimension( 300, 300 ) );
  }

  void this_windowClosing(WindowEvent e)
  {
    System.exit( 0 ) ;
  }

  public static void main(String[] args)
  {
    WordWrap wordWrap = new WordWrap();
    wordWrap.pack() ;
    wordWrap.show();
  }
}
ASKER CERTIFIED SOLUTION
Avatar of TimYates
TimYates
Flag of United Kingdom of Great Britain and Northern Ireland image

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
Simply returning false in getScrollableTracksViewportWidth() did not work for me.  For cases where the JTextPane's preferred size is less than that of the JScrollPane's viewport, I would get only a partially filled viewport.

My getScrollableTracksViewportWidth() in my extension of JTextPane looks like this:

    public boolean getScrollableTracksViewportWidth()
    {
        if (getParent() instanceof JViewport)
        {    
            if (getParent().getWidth() > document.getLongestLineWidth())
                return true;
        }

        return false;
    }

In the above code, document is a reference to a class that extends DefaultStyledDocument and implements the method getLongestLineWidth().  My implementation of that method is below.  (It includes things that are not relevant to this issue, like the PropertyChangeListener and the modified flag.  My appologies for this.)


import java.awt.Component;
import java.awt.Font;
import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
import java.util.ArrayList;

import javax.swing.JPanel;
import javax.swing.text.AttributeSet;
import javax.swing.text.BadLocationException;
import javax.swing.text.DefaultStyledDocument;
import javax.swing.text.Style;
import javax.swing.text.StyleConstants;
import javax.swing.text.StyleContext;

import org.apache.log4j.Logger;

/**
 * This Document for the text editor's text area handles highlighting.
 * @see javax.swing.text.PlainDocument
 */
public class TextEditorDocument extends DefaultStyledDocument implements LineNumberedDocument
{
    private static final Logger logger = Logger.getLogger(TextEditorDocument.class);
    public static final String FONT_NAME = "Monospaced";
    public static final int FONT_SIZE = 12;
    public static final String MODIFIED_PROPNAME = "modified";
    public static final char NEW_LINE_CHAR = '\n';
    public static final String NEW_LINE_STRING = "\n";
    private static final Component metricsComponent = new JPanel();
    private Style regularStyle = null;
    private boolean modified = false;
    /** An array of Integer objects that correspond to the offset for the start of each line */
    protected ArrayList offsets = new ArrayList();
    private PropertyChangeSupport changeSupport = null;

    public TextEditorDocument()
    {
        Style _default = StyleContext.getDefaultStyleContext().getStyle(StyleContext.DEFAULT_STYLE);
        StyleConstants.setFontFamily(_default, FONT_NAME);
        StyleConstants.setFontSize(_default, FONT_SIZE);
        regularStyle = this.addStyle("regular", _default);

        this.setLogicalStyle(0, regularStyle);
    }

    /**
     * Updates the offsets of each line affected and then updates the syntax
     * highlighting of those lines
     * @see javax.swing.text.Document#insertString(int, java.lang.String, javax.swing.text.AttributeSet)
     */
    public void insertString(int offs, String str, AttributeSet a) throws BadLocationException
    {
        if (str == null || str.length() == 0)
            return;
        int numLines = getNumLines();
        super.insertString(offs, str, a);
        setModified(true);

        int startLineIndex = getLineIndex(offs);
        int endLineIndex = updateOffsetsAfterInsert(offs, str);
        if (numLines != getNumLines())
            firePropertyChange(NUM_LINES_PROPNAME, new Integer(numLines), new Integer(getNumLines()));
    }

    /**
     * @see javax.swing.text.Document#remove(int, int)
     */
    public void remove(int offs, int len) throws BadLocationException
    {
        if (len <= 0)
            return;
        int numLines = getNumLines();
        String removedText = this.getText(offs, len);
        super.remove(offs, len);
        setModified(true);
        updateOffsetsAfterRemove(offs, removedText);
        if (numLines != getNumLines())
            firePropertyChange(NUM_LINES_PROPNAME, new Integer(numLines), new Integer(getNumLines()));
    }

    /**
     * Returns the index of the line in <code>lines</code> that corresponds
     * to the specified offset in the document.
     */
    protected int getLineIndex(int offset)
    {
        if (offsets.size() <= 1)
            return 0;

        int lineIndex = 0;
        for (int i = 0; i < offsets.size(); i++)
        {
            if (i == offsets.size()-1)
                return i;
            int nextLineOffset = ((Integer)offsets.get(i+1)).intValue();
            if (offset < nextLineOffset)
                return i;
        }
        throw new RuntimeException("shouldn't get here in getLineIndex(" + offset + ")");
    }

    /**
     * Gets the text of the line at the specified index
     * @param lineIndex the index of the line to return
     */
    public String getLine(int lineIndex)
    {
        if (lineIndex < 0 || lineIndex >= offsets.size())
            throw new IndexOutOfBoundsException("Index: " + lineIndex + ", Size: " + offsets.size());
        int lineOffset = ((Integer)offsets.get(lineIndex)).intValue();
        int lineLength = 0;
        if (offsets.size() == lineIndex + 1)
            lineLength = this.getLength() - lineOffset;
        else
            lineLength = ((Integer)offsets.get(lineIndex + 1)).intValue() - lineOffset;
        try
        {
            return this.getText(lineOffset, lineLength);
        }
        catch (BadLocationException e)
        {
            logger.error("lineOffset=" + lineOffset + " lineLength=" + lineLength +
                " offsets.size()=" + offsets.size() + " lineIndex=" + lineIndex, e);
            // since this method is only used internally, any error here is a
            // coding error
            throw new RuntimeException(e);
        }
    }

    /**
     * Returns the line number that corresponds to the specified offset.  This
     * is the same as the line index + 1.
     * @param offset the location in the document
     * @see #getLineIndex(int)
     */
    public int getLineNumber(int offset)
    {
        return 1 + getLineIndex(offset);
    }

    /**
     * Returns the character position relative to the line that corresponds
     * to the specified offset.
     * @param offset the location in the document
     */
    public int getCharacterPosition(int offset)
    {
        int lineIndex = getLineIndex(offset);
        int lineOffset = ((Integer)offsets.get(lineIndex)).intValue();
        return 1 + offset - lineOffset;
    }

    /**
     * Returns the number of lines of text in this document.
     */
    public int getNumLines()
    {
        return offsets.size();
    }

    /**
     * Returns the number of newline characters ('\n') that are found in the
     * specified string.
     */
    protected int countNewLines(String text)
    {
        int count = 0;
        for (int i = 0; i < text.length(); i++)
        {
            if (text.charAt(i) == NEW_LINE_CHAR)
                count++;
        }
        return count;
    }

    /**
     * Updates <code>offsets</code> after a remove.
     * @param offset the offset where the text was removed
     * @param insertedText the removed text
     */
    private void updateOffsetsAfterRemove(int offset, String removedText)
    {
        int interruptedLineIndex = getLineIndex(offset);

        // remove offsets for newlines that were deleted
        for (int i = countNewLines(removedText); i > 0; i--)
        {
            offsets.remove(interruptedLineIndex + 1);
        }

        for (int i = interruptedLineIndex + 1; i < offsets.size(); i++)
        {
            int curOffset = ((Integer)offsets.get(i)).intValue();
            offsets.set(i, new Integer(curOffset - removedText.length()));
        }
    }

    /**
     * Updates <code>offsets</code> after an insert.
     * @param offset the offset where the text was inserted
     * @param insertedText the inserted text
     * @return the index of the last line that was modified by the insert
     */
    private int updateOffsetsAfterInsert(int offset, String insertedText)
    {
        if (offsets.size() == 0)
            offsets.add(new Integer(0));

        int interruptedLineIndex = getLineIndex(offset);
        int startingOffset = ((Integer)offsets.get(interruptedLineIndex)).intValue();

        // increase the value of all the following offsets by the length
        // of the inserted text
        for (int i = interruptedLineIndex + 1; i < offsets.size(); i++)
        {
            int curOffset = ((Integer)offsets.get(i)).intValue();
            offsets.set(i, new Integer(curOffset + insertedText.length()));
        }

        // add new offsets for the inserted text
        int lineIndex = interruptedLineIndex + 1;
        for (int indexOfNewLine = -1; indexOfNewLine + 1 < insertedText.length(); lineIndex++)
        {
            indexOfNewLine = insertedText.indexOf(NEW_LINE_CHAR, indexOfNewLine+1);
            if (indexOfNewLine == -1)
                break;
            offsets.add(lineIndex, new Integer(offset + indexOfNewLine + 1));
        }
        return lineIndex - 1;
    }

    /**
     * Returns true if this document has been modified.
     * @return the <code>modified</code> flag
     */
    public boolean isModified()
    {
        return modified;
    }

    /**
     * Sets the <code>modified</code> flag to the specified value.
     */
    void setModified(boolean modified)
    {
        if (this.modified == modified)
            return;
        this.modified = modified;
        firePropertyChange(TextEditorDocument.MODIFIED_PROPNAME, new Boolean(!modified), new Boolean(modified));
    }
    /**
     * Support for reporting bound property changes for Object properties.
     * This method can be called when a bound property has changed and it will
     * send the appropriate PropertyChangeEvent to any registered
     * PropertyChangeListeners.
     *
     * @param propertyName the property whose value has changed
     * @param oldValue the property's previous value
     * @param newValue the property's new value
     */
    protected void firePropertyChange(String propertyName, Object oldValue, Object newValue)
    {
        if (changeSupport == null)
            return;
        changeSupport.firePropertyChange(propertyName, oldValue, newValue);
    }

    /**
     * Adds a PropertyChangeListener to the listener list. The listener is
     * registered for all bound properties of this class.
     * If listener is null, no exception is thrown and no action is performed.
     * @param propertyName  the name of the property to listen on
     * @param listener  the PropertyChangeListener to be added
     * @see #removePropertyChangeListener
     */
    public synchronized void addPropertyChangeListener(String propertyName, PropertyChangeListener listener)
    {
        if (listener == null)
            return;
        if (changeSupport == null)
            changeSupport = new PropertyChangeSupport(this);
        changeSupport.addPropertyChangeListener(propertyName, listener);
    }
 
    /**
     * Removes a PropertyChangeListener from the listener list. This method
     * should be used to remove PropertyChangeListeners that were registered
     * for all bound properties of this class.
     * <p>
     * If listener is null, no exception is thrown and no action is performed.
     * @param propertyName  the name of the property to listen on
     * @param listener the PropertyChangeListener to be removed
     * @see #addPropertyChangeListener
     */
    public synchronized void removePropertyChangeListener(String propertyName, PropertyChangeListener listener)
    {
        if (listener == null || changeSupport == null)
            return;
        changeSupport.removePropertyChangeListener(propertyName, listener);
    }

    /**
     * @see com.realvue.tools.texteditor.LineNumberedDocument#getNumCharsInLongestLine()
     */
    public int getLongestLineWidth()
    {
        int max = 0;
        Font font = StyleContext.getDefaultStyleContext().getFont(regularStyle);
        int numLines = getNumLines();
        for (int i = 0; i < numLines; i++)
        {
            char[] line = getLine(i).toCharArray();
            int lineWidth = metricsComponent.getFontMetrics(font).charsWidth(line, 0, line.length);
            if (lineWidth > max)
                max = lineWidth;
        }
        return max;

    }

}

The implementation of getLongestLineWidth() above will not be sufficient for everyone.  I only use a single (monospaced) font of a single size.  If this is not the case for you, you will need to sum up the character width of each character on the line using the font metrics of that character's font.

I hope this helps... somebody... somewhere.