Skip to Main Content

Java SE (Java Platform, Standard Edition)

Announcement

For appeals, questions and feedback about Oracle Forums, please email oracle-forums-moderators_us@oracle.com. Technical questions should be asked in the appropriate category. Thank you!

Interested in getting your voice heard by members of the Developer Marketing team at Oracle? Check out this post for AppDev or this post for AI focus group information.

Line Number in JTextPane

843804Jan 10 2005 — edited Jan 21 2005
Hi Experts;

How do I add a caret listener on this code so that it will just add a number
when the user goes to the next line.
import java.awt.*;
import javax.swing.*;
 
public class LineNumber extends JComponent
{
	private final static Color DEFAULT_BACKGROUND = new Color(213, 213, 234);
	private final static Color DEFAULT_FOREGROUND = Color.white;
	private final static Font DEFAULT_FONT = new Font("arial", Font.PLAIN, 11);
	
	// LineNumber height (abends when I use MAX_VALUE)
	private final static int HEIGHT = Integer.MAX_VALUE - 1000000;
	
	// Set right/left margin
	private final static int MARGIN = 5;
	
	// Line height of this LineNumber component
	private int lineHeight;
	
	// Line height of this LineNumber component
	private int fontLineHeight;
	
	// With of the LineNumber component
	private int currentRowWidth;
	
	// Metrics of this LineNumber component
	private FontMetrics fontMetrics;
	
	/**
	 * Convenience constructor for Text Components
	 */
	public LineNumber(JComponent component)
	{
		if (component == null)
		{
			setBackground( DEFAULT_BACKGROUND );
			setForeground( DEFAULT_FOREGROUND );
			setFont( DEFAULT_FONT );
		}
		else
		{
			setBackground( DEFAULT_BACKGROUND );
			setForeground( DEFAULT_FOREGROUND );
			setFont( component.getFont() );
		}
		
		setPreferredSize( 99 );
	}
	
	public void setPreferredSize(int row)
	{
		int width = fontMetrics.stringWidth( String.valueOf(row) );
		
		if (currentRowWidth < width)
		{
			currentRowWidth = width;
			setPreferredSize( new Dimension(2 * MARGIN + width, HEIGHT) );
		}
	}
	
	public void setFont(Font font)
	{
		super.setFont(font);
		fontMetrics = getFontMetrics( getFont() );
		fontLineHeight = fontMetrics.getHeight();
	}
	
	/**
	 * The line height defaults to the line height of the font for this
	 * component. The line height can be overridden by setting it to a
	 * positive non-zero value.
	 */
	public int getLineHeight()
	{
		if (lineHeight == 0)
			return fontLineHeight;
		else
			return lineHeight;
	}
	
	public void setLineHeight(int lineHeight)
	{
		if (lineHeight > 0)
			this.lineHeight = lineHeight;
	}
	
	public int getStartOffset()
	{
		return 4;
	}
	
	public void paintComponent(Graphics g)
	{
		 int lineHeight = getLineHeight();
		 int startOffset = getStartOffset();
		 Rectangle drawHere = g.getClipBounds();

		 g.setColor( getBackground() );
		 g.fillRect(drawHere.x, drawHere.y, drawHere.width, drawHere.height);

		 g.setColor( getForeground() );
		 int startLineNumber = (drawHere.y / lineHeight) + 1;
		 int endLineNumber = startLineNumber + (drawHere.height / lineHeight);

		 int start = (drawHere.y / lineHeight) * lineHeight + lineHeight - startOffset;

		 for (int i = startLineNumber; i <= endLineNumber; i++)
		 {
		 String lineNumber = String.valueOf(i);
		 int width = fontMetrics.stringWidth( lineNumber );
		 g.drawString(lineNumber, MARGIN + currentRowWidth - width, start);
		 start += lineHeight;
		 }

		 setPreferredSize( endLineNumber );
	}
} 
Thanks for your time . . .
The_Developer

Comments

843804
I don't have the slightest idea of what your LineNumber JComponent is for. Why passing a JComponent in the constructor when all you want is its Font?

Could you add a main method so that we can see how you intend to use it in the context of a real test?
843804
Joops its just for any text component.

Here's the code again, thanks :
 
import java.awt.*;
import javax.swing.*;
import javax.swing.border.*;
import javax.swing.text.*;

public class LineNumber extends JComponent
{
	private final static Color DEFAULT_BACKGROUND = new Color(230, 163, 4);
	private final static Color DEFAULT_FOREGROUND = Color.black;
	private final static Font DEFAULT_FONT = new Font("monospaced", Font.PLAIN, 12);
	
	// LineNumber height (abends when I use MAX_VALUE)
	private final static int HEIGHT = Integer.MAX_VALUE - 1000000;
	
	// Set right/left margin
	private final static int MARGIN = 5;
	
	// Line height of this LineNumber component
	private int lineHeight;
	
	// Line height of this LineNumber component
	private int fontLineHeight;
	
	//
	private int currentRowWidth;
	
	// Metrics of this LineNumber component
	private FontMetrics fontMetrics;
	
	/**
	 * Convenience constructor for Text Components
	 */
	public LineNumber(JComponent component)
	{
		if (component == null)
		{
			setBackground( DEFAULT_BACKGROUND );
			setForeground( DEFAULT_FOREGROUND );
			setFont( DEFAULT_FONT );
		}
		else
		{
			setBackground( DEFAULT_BACKGROUND );
			setForeground( component.getForeground() );
			setFont( component.getFont() );
		}
		
		setPreferredSize( 9999 );
	}
	
	public void setPreferredSize(int row)
	{
		int width = fontMetrics.stringWidth( String.valueOf(row) );
		
		if (currentRowWidth < width)
		{
			currentRowWidth = width;
			setPreferredSize( new Dimension(2 * MARGIN + width, HEIGHT) );
		}
	}
	
	public void setFont(Font font)
	{
		super.setFont(font);
		fontMetrics = getFontMetrics( getFont() );
		fontLineHeight = fontMetrics.getHeight();
	}
	
	/**
	 * The line height defaults to the line height of the font for this
	 * component. The line height can be overridden by setting it to a
	 * positive non-zero value.
	 */
	public int getLineHeight()
	{
		if (lineHeight == 0)
			return fontLineHeight;
		else
			return lineHeight;
	}
	
	public void setLineHeight(int lineHeight)
	{
		if (lineHeight > 0)
			this.lineHeight = lineHeight;
	}
	
	public int getStartOffset()
	{
		return 4;
	}
	
	public void paintComponent(Graphics g)
	{
		int lineHeight = getLineHeight();
		int startOffset = getStartOffset();
		Rectangle drawHere = g.getClipBounds();
		// System.out.println( drawHere );
		
		// Paint the background
		
		g.setColor( getBackground() );
		g.fillRect(drawHere.x, drawHere.y, drawHere.width, drawHere.height);
		
		// Determine the number of lines to draw in the foreground.
		
		g.setColor( getForeground() );
		int startLineNumber = (drawHere.y / lineHeight) + 1;
		int endLineNumber = startLineNumber + (drawHere.height / lineHeight);
		
		int start = (drawHere.y / lineHeight) * lineHeight + lineHeight - startOffset;
		
		// System.out.println( startLineNumber + " : " + endLineNumber + " : " + start );
		
		for (int i = startLineNumber; i <= endLineNumber; i++)
		{
			String lineNumber = String.valueOf(i);
			int width = fontMetrics.stringWidth( lineNumber );
			g.drawString(lineNumber, MARGIN + currentRowWidth - width, start);
			start += lineHeight;
		}
		
		setPreferredSize( endLineNumber );
	}
	
	public static void main(String[] args)
	{
		JFrame frame = new JFrame("LineNumberDemo");
		frame.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );
		
		JPanel panel = new JPanel();
		frame.setContentPane( panel );
		panel.setBorder(BorderFactory.createEmptyBorder(20,20,20,20));
		panel.setLayout(new BorderLayout());
		
		JTextPane textPane = new JTextPane();
		
		JScrollPane scrollPane = new JScrollPane(textPane);
		panel.add(scrollPane);
		scrollPane.setPreferredSize(new Dimension(300, 250));
		
		LineNumber lineNumber = new LineNumber( textPane );
		lineNumber.setPreferredSize(99999);
		scrollPane.setRowHeaderView( lineNumber );
		
		frame.pack();
		frame.setVisible(true);
	}
}
843804
Anyone wants to help ?
843804
How do I add a caret listener on this code so that it will just add a number
when the user goes to the next line.
843804
I'm working on it. I have to admit it's kinda tricky. I have a question : do you want to show line numbers for all lines (including those wrapped lines) or just lines after a '\n' (in the paragraph meaning of a line)?
Also, I'm wondering why you want to add a Caret listener. If you want to react on updates to the document, add a DocumentListener to the Document and implement the three methods (insertUpdate, removeUpdate, changeUpdate).
843804
Sure do whatever you want. I just wanna show the who'll line bar and show the
numbers if the user goes to a new line. like : an ide.

| 1 |
| 2 |
| 3 | This is where the caret is : |
| |
| |

So if the user press enter or goes to a new line the next number displays.
843804
If we can finish this. It would be one good free line number for all :)
843804
No problem, as soon as I have something worth it, I'll post it.
The thing is : most IDEs I know don't wrap lines so it's easier for them. I still don't know what your exact requirements are on the matter. Try and resize your frame to see what happens.
I also am trying to extend your functionality in the case of a StyledDocument where the Font may change from one line to the other.
843804
Try and resize your frame to see what happens.
There is no problem there. The problem is from my codes, my codes automatically generates all the numbers, it calculates it all even if you maximize or add 1000 lines of codes. What I wanna do is to illiminate that by just showing the number in every new line.
843804
There is no problem there. The problem is from my
codes, my codes automatically generates all the
numbers, it calculates it all even if you maximize or
add 1000 lines of codes. What I wanna do is to
illiminate that by just showing the number in every
new line.
My bad. I'm already working on a version that updates automatically (using a DocumentListener). My question was about whether you wanted the number of lines to remain the same when resizing the JTextComponent (equivalent to counting Paragraphs) or you wanted to take into account the wrapped lines. From your answers, I guess you want to count wrapped lines as well.
843804
I guess thats it, i'lll be waiting, thank you very much.
843804
Well, I guess you'll be disappointed. I was not able to implement the desired behavior. I am stuck with this line wrap problem. It's actually not that easy to get a hold on the wrapped lines. I also used a JTextArea instead of a JTextPane since styles didn't seem to be an issue. I also gave up the rowHeaderView system as I was facing major painting problems. I don't know, maybe I missed something but repainting while scrolling was completely screwed up. Anyway, I hope you'll get the idea :
import java.awt.*;

import javax.swing.*;
import javax.swing.event.DocumentListener;
import javax.swing.event.DocumentEvent;
import javax.swing.text.BadLocationException;

public class LineNumberedTextArea extends JPanel {
	private final static Color DEFAULT_BACKGROUND = new Color(230, 163, 4);
	private final static int MARGIN = 5;
	private JTextArea theTextArea;
	private LineNumberPanel theLineNumberPanel;
	private boolean isWrapped;

	private class LineNumberPanel extends JPanel implements DocumentListener {
		private FontMetrics theFontMetrics;
		private int currentRowWidth;

		public LineNumberPanel() {
			setOpaque(true);
			setBackground(DEFAULT_BACKGROUND);
			setFont(theTextArea.getFont());
			theFontMetrics = getFontMetrics(getFont());
			setForeground(theTextArea.getForeground());
			currentRowWidth = getDesiredRowWidth();
		}

		private void update() {
			int desiredRowWidth = getDesiredRowWidth();
			if (desiredRowWidth != currentRowWidth) {
				currentRowWidth = desiredRowWidth;
				revalidate();
			}
			repaint();
		}

		private int getDesiredRowWidth() {
			int result = 0;
			try {
				int nbLines = theTextArea.getLineOfOffset(theTextArea.getText().length()) + 1;
				result = theFontMetrics.stringWidth(Integer.toString(nbLines));
			} catch (BadLocationException e) {
				e.printStackTrace();
			}
			return result;
		}

		public void paintComponent(Graphics g) {
			super.paintComponent(g);
			try {
				int startLine = theTextArea.getLineOfOffset(0);
				int endline = theTextArea.getLineOfOffset(theTextArea.getText().length());

				for (int line = startLine; line <= endline; line++) {
					int y = theTextArea.modelToView(theTextArea.getLineStartOffset(line)).y + theFontMetrics.getHeight() - theFontMetrics.getDescent();
					String s = Integer.toString(line + 1);
					int width = theFontMetrics.stringWidth(s);
					g.drawString(s, MARGIN + currentRowWidth - width, y);
				}
			} catch (BadLocationException e) {
				e.printStackTrace();
			}
		}

		public Dimension getPreferredSize() {
			return new Dimension(2 * MARGIN + currentRowWidth, theTextArea.getHeight());
		}

		public void insertUpdate(DocumentEvent e) {
			update();
		}

		public void removeUpdate(DocumentEvent e) {
			update();
		}

		public void changedUpdate(DocumentEvent e) {
			update();
		}
	}

	public LineNumberedTextArea(boolean wrap) {
		super(new BorderLayout(0, 0));
		isWrapped = wrap;
		theTextArea = new JTextArea();
		theTextArea.setLineWrap(isWrapped);
		theTextArea.setWrapStyleWord(isWrapped);
		add(theTextArea, BorderLayout.CENTER);
		theLineNumberPanel = new LineNumberPanel();
		add(theLineNumberPanel, BorderLayout.WEST);
		theTextArea.getDocument().addDocumentListener(theLineNumberPanel);
	}

	public Dimension getPreferredSize() {
		Dimension dim = super.getPreferredSize();
		if (isWrapped) {
			dim.width = 1;
		}
		return dim;
	}

	public JTextArea getTextArea() {
		return theTextArea;
	}

	public static void main(String[] args) {
		final JFrame frame = new JFrame();
		frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		frame.setContentPane(new JScrollPane(new LineNumberedTextArea(false)));
		SwingUtilities.invokeLater(new Runnable() {
			public void run() {
				frame.setSize(new Dimension(400, 400));
				frame.show();
			}
		});
	}
}
If you set the wrap to false, it does exactly what you wanted. I'll try and improve it tomorrow (it's getting late here).
I forgot to mention that the overriding of getPreferredSize on the top panel is a famous hack when dealing with JTextAreas that don't want to wrap.
843804
Here's what I use. It behaves correctly WRT wrapped lines, and should work equally well with a JTextArea or a JTextPane.
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Rectangle;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;

import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;

import javax.swing.BorderFactory;
import javax.swing.JComponent;
import javax.swing.SizeSequence;
import javax.swing.UIManager;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import javax.swing.text.BadLocationException;
import javax.swing.text.Document;
import javax.swing.text.Element;
import javax.swing.text.JTextComponent;

/**
 * LineNumberView is a simple line-number gutter that works correctly
 * even when lines are wrapped in the associated text component.  This
 * is meant to be used as the RowHeaderView in a JScrollPane that
 * contains the associated text component.  Example usage:
 *<pre>
 *   JTextArea ta = new JTextArea();
 *   ta.setLineWrap(true);
 *   ta.setWrapStyleWord(true);
 *   JScrollPane sp = new JScrollPane(ta);
 *   sp.setRowHeaderView(new LineNumberView(ta));
 *</pre>
 *
 * @author Alan Moore
 */
public class LineNumberView extends JComponent
{
  // This is for the border to the right of the line numbers.
  // There's probably a UIDefaults value that could be used for this.
  private static final Color BORDER_COLOR = Color.GRAY;

  private static final int WIDTH_TEMPLATE = 99999;
  private static final int MARGIN = 5;

  private FontMetrics viewFontMetrics;
  private int maxNumberWidth;
  private int componentWidth;

  private int textTopInset;
  private int textFontAscent;
  private int textFontHeight;

  private JTextComponent text;
  private SizeSequence sizes;
  private int startLine = 0;
  private boolean structureChanged = true;

  /**
   * Construct a LineNumberView and attach it to the given text component.
   * The LineNumberView will listen for certain kinds of events from the
   * text component and update itself accordingly.
   * 
   * @param startLine the line that changed, if there's only one
   * @param structureChanged if <tt>true</tt>, ignore the line number and 
   *     update all the line heights.
   */
  public LineNumberView(JTextComponent text)
  {
    if (text == null)
    {
      throw new IllegalArgumentException("Text component cannot be null");
    }
    this.text = text;
    updateCachedMetrics();

    UpdateHandler handler = new UpdateHandler();
    text.getDocument().addDocumentListener(handler);
    text.addPropertyChangeListener(handler);
    text.addComponentListener(handler);

    setBorder(BorderFactory.createMatteBorder(0, 0, 0, 1, BORDER_COLOR));
  }

  /**
   * Schedule a repaint because one or more line heights may have changed.
   * 
   * @param startLine the line that changed, if there's only one
   * @param structureChanged if <tt>true</tt>, ignore the line number and 
   *     update all the line heights.
   */
  private void viewChanged(int startLine, boolean structureChanged)
  {
    this.startLine = startLine;
    this.structureChanged = structureChanged;

    revalidate();
    repaint();
  }

  /** Update the line heights as needed. */
  private void updateSizes()
  {
    if (startLine < 0)
    {
      return;
    }

    if (structureChanged)
    {
      int count = getAdjustedLineCount();
      sizes = new SizeSequence(count);
      for (int i = 0; i < count; i++)
      {
        sizes.setSize(i, getLineHeight(i));
      }
      structureChanged = false;
    }
    else
    {
      sizes.setSize(startLine, getLineHeight(startLine));
    }

    startLine = -1;
  }

  /* Copied from javax.swing.text.PlainDocument */
  private int getAdjustedLineCount()
  {
    // There is an implicit break being modeled at the end of the
    // document to deal with boundary conditions at the end.  This
    // is not desired in the line count, so we detect it and remove
    // its effect if throwing off the count.
    Element map = text.getDocument().getDefaultRootElement();
    int n = map.getElementCount();
    Element lastLine = map.getElement(n - 1);
    if ((lastLine.getEndOffset() - lastLine.getStartOffset()) > 1)
    {
      return n;
    }

    return n - 1;
  }

  /**
   * Get the height of a line from the JTextComponent.
   * 
   * @param index the line number
   * @param the height, in pixels
   */
  private int getLineHeight(int index)
  {
    int lastPos = sizes.getPosition(index) + textTopInset;
    int height = textFontHeight;
    try
    {
      Element map = text.getDocument().getDefaultRootElement();
      int lastChar = map.getElement(index).getEndOffset() - 1;
      Rectangle r = text.modelToView(lastChar);
      height = (r.y - lastPos) + r.height;
    }
    catch (BadLocationException ex)
    {
      ex.printStackTrace();
    }
    return height;
  }

  /**
   * Cache some values that are used a lot in painting or size
   * calculations. Also ensures that the line-number font is not
   * larger than the text component's font (by point-size, anyway).
   */
  private void updateCachedMetrics()
  {
    Font textFont = text.getFont();
    FontMetrics fm = getFontMetrics(textFont);
    textFontHeight = fm.getHeight();
    textFontAscent = fm.getAscent();
    textTopInset = text.getInsets().top;

    Font viewFont = getFont();
    boolean changed = false;
    if (viewFont == null)
    {
      viewFont = UIManager.getFont("Label.font");
      changed = true;
    }
    if (viewFont.getSize() > textFont.getSize())
    {
      viewFont = viewFont.deriveFont(textFont.getSize2D());
      changed = true;
    }

    viewFontMetrics = getFontMetrics(viewFont);
    maxNumberWidth = viewFontMetrics.stringWidth(String.valueOf(WIDTH_TEMPLATE));
    componentWidth = 2 * MARGIN + maxNumberWidth;

    if (changed)
    {
      super.setFont(viewFont);
    }
  }

  public Dimension getPreferredSize()
  {
    return new Dimension(componentWidth, text.getHeight());
  }

  public void setFont(Font font)
  {
    super.setFont(font);
    updateCachedMetrics();
  }

  public void paintComponent(Graphics g)
  {
    updateSizes();
    Rectangle clip = g.getClipBounds();

    g.setColor(getBackground());
    g.fillRect(clip.x, clip.y, clip.width, clip.height);

    g.setColor(getForeground());
    int base = clip.y - textTopInset;
    int first = sizes.getIndex(base);
    int last = sizes.getIndex(base + clip.height);
    String text = "";
    
    for (int i = first; i <= last; i++)
    {
      text = String.valueOf(i+1);
      int x = MARGIN + maxNumberWidth - viewFontMetrics.stringWidth(text);
      int y = sizes.getPosition(i) + textFontAscent + textTopInset;
      g.drawString(text, x, y);
    }
  }

  class UpdateHandler extends ComponentAdapter
      implements PropertyChangeListener, DocumentListener
  {
    /**
     * The text component was resized. 'Nuff said.
     */
    public void componentResized(ComponentEvent evt)
    {
      viewChanged(0, true);
    }

    /**
     * A bound property was changed on the text component. Properties
     * like the font, border, and tab size affect the layout of the
     * whole document, so we invalidate all the line heights here.
     */
    public void propertyChange(PropertyChangeEvent evt)
    {
      Object oldValue = evt.getOldValue();
      Object newValue = evt.getNewValue();
      String propertyName = evt.getPropertyName();
      if ("document".equals(propertyName))
      {
        if (oldValue != null && oldValue instanceof Document)
        {
          ((Document)oldValue).removeDocumentListener(this);
        }
        if (newValue != null && newValue instanceof Document)
        {
          ((Document)newValue).addDocumentListener(this);
        }
      }

      updateCachedMetrics();
      viewChanged(0, true);
    }

    /**
     * Text was inserted into the document.
     */
    public void insertUpdate(DocumentEvent evt)
    {
      update(evt);
    }
  
    /**
     * Text was removed from the document.
     */
    public void removeUpdate(DocumentEvent evt)
    {
      update(evt);
    }
  
    /**
     * Text attributes were changed.  In a source-code editor based on
     * StyledDocument, attribute changes should be applied automatically
     * in response to inserts and removals.  Since we're already 
     * listening for those, this method should be redundant, but YMMV.
     */
    public void changedUpdate(DocumentEvent evt)
    {
//      update(evt);
    }
  
    /**
     * If the edit was confined to a single line, invalidate that
     * line's height.  Otherwise, invalidate them all.
     */
    private void update(DocumentEvent evt)
    {
      Element map = text.getDocument().getDefaultRootElement();
      int line = map.getElementIndex(evt.getOffset());
      DocumentEvent.ElementChange ec = evt.getChange(map);
      viewChanged(line, ec != null);
    }
  }
}
843804
Nice piece of code indeed. Glad you were able to work out the flickering problems I was facing while scrolling.

One last detail though : your solution behaves pretty much like mine in that wrapped lines are not added. I wonder if there is a simple solution to it.
843804
Its good and does what I want but when the line number reach 1000 lines of codes the area doesnt seem to adjust or it doesnt expand ? how do we fix that ?
843804
Thanks for your codes Can you convert that for JTextPane ?
843804
I don't know if you're talking about my version or uncle_alice's.
I thought mine would revalidate in such a case.
As for the conversion to JTextPane, it shouldn't be a problem. I'll do it.
843804
Thanks uncle_alice's version was good but does not resize.
I'll be waiting for the JTextPane version, thanks.
843804
@weebib: What do you mean by wrapped lines are not added? In my editor, the line number is aligned with the first row of the associated logical line; if the line is wrapped, there's a blank space below the number. Is that not what you're seeing?

@The_Dev: Do you mean when the line count goes over 100,000? Because it should be able to handle that much as is. But you're right: it should adjust to accomodate larger numbers. I've never bothered to code that because I never edit files that large. I'll look into that, but in the meantime, you can always add more nines to WIDTH_TEMPLATE.
843804
uncle_alice : If you look at and test my version, although it is not as elegant as yours, both behaviors are almost identical. I was merely trying to say that there is another behavior than the one we both implemented. I seems a bit harder to code. It would mean adding visible lines, not strictly logical ones : the total number of lines would depend on the current JTextComponent's size.
843804
Thanks uncle_alice's version was good but does not
resize.
I'll be waiting for the JTextPane version, thanks.
I suggest you give a much more thorough look at uncle_alice's version and give him the dukes. It has very interesting optimizations that make the repainting much faster. In any case, here is my final version that you might use to modify his :
import java.awt.*;

import javax.swing.*;
import javax.swing.event.DocumentListener;
import javax.swing.event.DocumentEvent;
import javax.swing.text.BadLocationException;
import javax.swing.text.JTextComponent;
import javax.swing.text.Document;
import javax.swing.text.Element;

public class LineNumberPanel extends JPanel implements DocumentListener {
	private final static Color DEFAULT_BACKGROUND = new Color(230, 163, 4);
	private final static int MARGIN = 5;
	private JTextComponent theTextComponent;
	private FontMetrics theFontMetrics;
	private int currentRowWidth;

	public LineNumberPanel(JTextComponent aTextComponent) {
		theTextComponent = aTextComponent;
		theTextComponent.getDocument().addDocumentListener(this);
		setOpaque(true);
		setBackground(DEFAULT_BACKGROUND);
		setFont(theTextComponent.getFont());
		theFontMetrics = getFontMetrics(getFont());
		setForeground(theTextComponent.getForeground());
		currentRowWidth = getDesiredRowWidth();
	}

	private void update() {
		int desiredRowWidth = getDesiredRowWidth();
		if (desiredRowWidth != currentRowWidth) {
			currentRowWidth = desiredRowWidth;
			revalidate();
		}
		repaint();
	}

	private int getDesiredRowWidth() {
		Document doc = theTextComponent.getDocument();
		int length = doc.getLength();
		Element map = doc.getDefaultRootElement();
		int nbLines =  map.getElementIndex(length) + 1;
		return theFontMetrics.stringWidth(Integer.toString(nbLines));
	}

	public void paintComponent(Graphics g) {
		super.paintComponent(g);
		try {
			Document doc = theTextComponent.getDocument();
			int length = doc.getLength();
			Element map = doc.getDefaultRootElement();
			int startLine =  map.getElementIndex(0);
			int endline = map.getElementIndex(length);
			for (int line = startLine; line <= endline; line++) {
				int y = theTextComponent.modelToView(map.getElement(line).getStartOffset()).y + theFontMetrics.getHeight() - theFontMetrics.getDescent();
				String s = Integer.toString(line + 1);
				int width = theFontMetrics.stringWidth(s);
				g.drawString(s, MARGIN + currentRowWidth - width, y);
			}
		} catch (BadLocationException e) {
			e.printStackTrace();
		}

	}

	public Dimension getPreferredSize() {
		return new Dimension(2 * MARGIN + currentRowWidth, theTextComponent.getHeight());
	}

	public void insertUpdate(DocumentEvent e) {
		update();
	}

	public void removeUpdate(DocumentEvent e) {
		update();
	}

	public void changedUpdate(DocumentEvent e) {
		update();
	}

	public static void main(String[] args) {
		final JFrame frame = new JFrame();
		frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		JTextPane textPane = new JTextPane();
		JScrollPane scrollPane = new JScrollPane(textPane);
		scrollPane.setRowHeaderView(new LineNumberPanel(textPane));
		frame.setContentPane(scrollPane);
		SwingUtilities.invokeLater(new Runnable() {
			public void run() {
				frame.setSize(new Dimension(400, 400));
				frame.show();
			}
		});
	}
}
843804
http://www.developer.com/java/other/article.php/3318421

check this link. Hope this helps.

Stas
843804
Can make the changes for uncle_alice's version because it does not resize.
843804
I cant get uncle_alice's version to resize ? can you help.

Or on my previous codes it handles the line wraping we just have to change the code from this :

for (int i = startLineNumber; i <= endLineNumber; i++) {

String lineNumber = String.valueOf(i);
int width = fontMetrics.stringWidth( lineNumber );
g.drawString(lineNumber, MARGIN + currentRowWidth - width, start + 5);
start += lineHeight;
}
843804
Very nice, Stas! That solves the problem of numbering by visible line instead of by logical line, if that's what you need. It has the same problem as mine, though, in that the width of the gutter is hard-coded. Once they get over 999, the line numbers become unreadable. Below is an updated version of LineNumberView with fully dynamic width setting. I had some trouble getting it to use the correct width when first displayed, but I think I've got that licked. Let me know if you have any problems with it.
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Rectangle;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;

import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;

import javax.swing.*;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import javax.swing.text.BadLocationException;
import javax.swing.text.Document;
import javax.swing.text.Element;
import javax.swing.text.JTextComponent;

/**
 * LineNumberView is a simple line-number gutter that works correctly
 * even when lines are wrapped in the associated text component.  This
 * is meant to be used as the RowHeaderView in a JScrollPane that
 * contains the associated text component.  Example usage:
 *<pre>
 *   JTextArea ta = new JTextArea();
 *   ta.setLineWrap(true);
 *   ta.setWrapStyleWord(true);
 *   JScrollPane sp = new JScrollPane(ta);
 *   sp.setRowHeaderView(new LineNumberView(ta));
 *</pre>
 *
 * @author Alan Moore
 */
public class LineNumberView extends JComponent
{
  // This is for the border to the right of the line numbers.
  // There's probably a UIDefaults value that could be used for this.
  private static final Color BORDER_COLOR = Color.GRAY;

  // The minimum width of the gutter, in number of digits
  private static final int MIN_DIGITS = 3;

  private static final int MARGIN = 5;

  private FontMetrics viewFontMetrics;
  private int digitWidth;
  private int maxDigits;
  private int componentWidth;

  private int textTopInset;
  private int textFontAscent;
  private int textFontHeight;

  private JTextComponent text;
  private SizeSequence sizes;
  private int startLine = 0;
  private boolean structureChanged = true;

  /**
   * Construct a LineNumberView and attach it to the given text component.
   * The LineNumberView will listen for certain kinds of events from the
   * text component and update itself accordingly.
   * 
   * @param text the associated text component
   */
  public LineNumberView(JTextComponent text)
  {
    if (text == null)
    {
      throw new IllegalArgumentException("Text component cannot be null");
    }
    this.text = text;
    updateCachedMetrics();

    UpdateHandler handler = new UpdateHandler();
    text.getDocument().addDocumentListener(handler);
    text.addPropertyChangeListener(handler);
    text.addComponentListener(handler);

    setBorder(BorderFactory.createMatteBorder(0, 0, 0, 1, BORDER_COLOR));
  }

  /**
   * Schedule a repaint because one or more line heights may have changed.
   * 
   * @param startLine the line that changed, if there's only one
   * @param structureChanged if <tt>true</tt>, ignore the line number and 
   *     update all the line heights.
   */
  private void viewChanged(int startLine, boolean structureChanged)
  {
    this.startLine = startLine;
    this.structureChanged = structureChanged;
    if (structureChanged)
    {
      maxDigits = 0;
      componentWidth = 0;
    }
    revalidate();
    repaint();
  }

  /** Update the line heights as needed. */
  private void updateSizes()
  {
    if (startLine < 0)
    {
      return;
    }

    if (structureChanged)
    {
      int count = getAdjustedLineCount();
      sizes = new SizeSequence(count);
      for (int i = 0; i < count; i++)
      {
        sizes.setSize(i, getLineHeight(i));
      }
      structureChanged = false;
    }
    else
    {
      sizes.setSize(startLine, getLineHeight(startLine));
    }

    startLine = -1;
  }

  private int getMaxDigits()
  {
    if (maxDigits <= 0)
    {
      int lineCount = getAdjustedLineCount();
      maxDigits = Math.max(MIN_DIGITS, String.valueOf(lineCount).length());
    }
    return maxDigits;
  }

  private int getComponentWidth()
  {
    if (componentWidth <= 0)
    {
      componentWidth = getMaxDigits() * digitWidth + MARGIN * 2;
    }
    return componentWidth;
  }

  /* Copied from javax.swing.text.PlainDocument */
  private int getAdjustedLineCount()
  {
    // There is an implicit break being modeled at the end of the
    // document to deal with boundary conditions at the end.  This
    // is not desired in the line count, so we detect it and remove
    // its effect if throwing off the count.
    Element map = text.getDocument().getDefaultRootElement();
    int n = map.getElementCount();
    Element lastLine = map.getElement(n - 1);
    if ((lastLine.getEndOffset() - lastLine.getStartOffset()) > 1)
    {
      return n;
    }

    return n - 1;
  }

  /**
   * Get the height of a line from the JTextComponent.
   * 
   * @param index the line number
   * @param the height, in pixels
   */
  private int getLineHeight(int index)
  {
    int lastPos = sizes.getPosition(index) + textTopInset;
    int height = textFontHeight;
    try
    {
      Element map = text.getDocument().getDefaultRootElement();
      int lastChar = map.getElement(index).getEndOffset() - 1;
      Rectangle r = text.modelToView(lastChar);
      height = (r.y - lastPos) + r.height;
    }
    catch (BadLocationException ex)
    {
      ex.printStackTrace();
    }
    return height;
  }

  /**
   * Cache some values that are used a lot in painting or size
   * calculations. Also ensures that the line-number font is not
   * larger than the text component's font (by point-size, anyway).
   */
  private void updateCachedMetrics()
  {
    Font textFont = text.getFont();
    FontMetrics fm = getFontMetrics(textFont);
    textFontHeight = fm.getHeight();
    textFontAscent = fm.getAscent();
    textTopInset = text.getInsets().top;

    Font viewFont = getFont();
    boolean changed = false;
    if (viewFont == null)
    {
      viewFont = UIManager.getFont("Label.font");
      changed = true;
    }
    if (viewFont.getSize() > textFont.getSize())
    {
      viewFont = viewFont.deriveFont(textFont.getSize2D());
      changed = true;
    }

    viewFontMetrics = getFontMetrics(viewFont);
    digitWidth = viewFontMetrics.stringWidth("0");

    if (changed)
    {
      super.setFont(viewFont);
    }
  }

  public Dimension getPreferredSize()
  {
    return new Dimension(getComponentWidth(), text.getHeight());
  }

  public void setFont(Font font)
  {
    super.setFont(font);
    updateCachedMetrics();
  }

  public void paintComponent(Graphics g)
  {
    updateSizes();
    Rectangle clip = g.getClipBounds();

    g.setColor(getBackground());
    g.fillRect(clip.x, clip.y, clip.width, clip.height);

    g.setColor(getForeground());
    int base = clip.y - textTopInset;
    int first = sizes.getIndex(base);
    int last = sizes.getIndex(base + clip.height);
    String text = "";
    
    for (int i = first; i <= last; i++)
    {
      text = String.valueOf(i+1);
      int x = getComponentWidth() - MARGIN - viewFontMetrics.stringWidth(text);
      int y = sizes.getPosition(i) + textFontAscent + textTopInset;
      g.drawString(text, x, y);
    }
  }


  class UpdateHandler extends ComponentAdapter
      implements PropertyChangeListener, DocumentListener
  {
    /**
     * The text component was resized.
     */
    public void componentResized(ComponentEvent evt)
    {
      viewChanged(0, true);
    }

    /**
     * A bound property was changed on the text component. Properties
     * like the font, border, and tab size affect the layout of the
     * whole document, so we invalidate all the line heights here.
     */
    public void propertyChange(PropertyChangeEvent evt)
    {
      Object oldValue = evt.getOldValue();
      Object newValue = evt.getNewValue();
      String propertyName = evt.getPropertyName();
      if ("document".equals(propertyName))
      {
        if (oldValue != null && oldValue instanceof Document)
        {
          ((Document)oldValue).removeDocumentListener(this);
        }
        if (newValue != null && newValue instanceof Document)
        {
          ((Document)newValue).addDocumentListener(this);
        }
      }

      updateCachedMetrics();
      viewChanged(0, true);
    }

    /**
     * Text was inserted into the document.
     */
    public void insertUpdate(DocumentEvent evt)
    {
      update(evt);
    }
  
    /**
     * Text was removed from the document.
     */
    public void removeUpdate(DocumentEvent evt)
    {
      update(evt);
    }
  
    /**
     * Text attributes were changed.  In a source-code editor based on
     * StyledDocument, attribute changes should be applied automatically
     * in response to inserts and removals.  Since we're already 
     * listening for those, this method should be redundant, but YMMV.
     */
    public void changedUpdate(DocumentEvent evt)
    {
//      update(evt);
    }
  
    /**
     * If the edit was confined to a single line, invalidate that
     * line's height.  Otherwise, invalidate them all.
     */
    private void update(DocumentEvent evt)
    {
      Element map = text.getDocument().getDefaultRootElement();
      int line = map.getElementIndex(evt.getOffset());
      DocumentEvent.ElementChange ec = evt.getChange(map);
      viewChanged(line, ec != null);
    }
  }

  public static void main(String[] args)
  {
    final JFrame frame = new JFrame();
    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    JTextPane textPane = new JTextPane();
    JScrollPane scrollPane = new JScrollPane(textPane);
    scrollPane.setRowHeaderView(new LineNumberView(textPane));
    frame.setContentPane(scrollPane);
    SwingUtilities.invokeLater(
          new Runnable()
          {
            public void run()
            {
              frame.setSize(new Dimension(400, 400));
              frame.setLocationRelativeTo(null);
              frame.setVisible(true);
            }
          });
  }
}
843804
I found a bit of a bug, I tried inserting some strings but the line number overlaps the string by one line. How do we fix that ?
One more thing how do I set my own font in your codes ? I tried altering it but it seems to be not working.
/**
 * Created on Jan 14, 2005
 *
 * @author The_Developer
 *
 * TODO To change the template for this generated type comment go to
 * Window - Preferences - Java - Code Style - Code Templates
 */
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Rectangle;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;

import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.Date;

import javax.swing.*;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import javax.swing.text.BadLocationException;
import javax.swing.text.Document;
import javax.swing.text.Element;
import javax.swing.text.JTextComponent;

/**
 * LineNumberView is a simple line-number gutter that works correctly
 * even when lines are wrapped in the associated text component.  This
 * is meant to be used as the RowHeaderView in a JScrollPane that
 * contains the associated text component.  Example usage:
 *<pre>
 *   JTextArea ta = new JTextArea();
 *   ta.setLineWrap(true);
 *   ta.setWrapStyleWord(true);
 *   JScrollPane sp = new JScrollPane(ta);
 *   sp.setRowHeaderView(new LineNumberView(ta));
 *</pre>
 *
 * @author Alan Moore
 */
public class LineNumberView extends JComponent
{
	// This is for the border to the right of the line numbers.
	// There's probably a UIDefaults value that could be used for this.
	private static final Color BORDER_COLOR = Color.GRAY;
	
	// The minimum width of the gutter, in number of digits
	private static final int MIN_DIGITS = 3;
	
	private static final int MARGIN = 5;
	
	private FontMetrics viewFontMetrics;
	private int digitWidth;
	private int maxDigits;
	private int componentWidth;
	
	private int textTopInset;
	private int textFontAscent;
	private int textFontHeight;
	
	private JTextComponent text;
	private SizeSequence sizes;
	private int startLine = 0;
	private boolean structureChanged = true;
	
	/**
	 * Construct a LineNumberView and attach it to the given text component.
	 * The LineNumberView will listen for certain kinds of events from the
	 * text component and update itself accordingly.
	 * 
	 * @param text the associated text component
	 */
	public LineNumberView(JTextComponent text)
	{
		if (text == null)
		{
			throw new IllegalArgumentException("Text component cannot be null");
		}
		this.text = text;
		updateCachedMetrics();
		
		UpdateHandler handler = new UpdateHandler();
		text.getDocument().addDocumentListener(handler);
		text.addPropertyChangeListener(handler);
		text.addComponentListener(handler);
		
		setBorder(BorderFactory.createMatteBorder(0, 0, 0, 1, BORDER_COLOR));
	}
	
	/**
	 * Schedule a repaint because one or more line heights may have changed.
	 * 
	 * @param startLine the line that changed, if there's only one
	 * @param structureChanged if <tt>true</tt>, ignore the line number and 
	 *     update all the line heights.
	 */
	private void viewChanged(int startLine, boolean structureChanged)
	{
		this.startLine = startLine;
		this.structureChanged = structureChanged;
		if (structureChanged)
		{
			maxDigits = 0;
			componentWidth = 0;
		}
		revalidate();
		repaint();
	}
	
	/** Update the line heights as needed. */
	private void updateSizes()
	{
		if (startLine < 0)
		{
			return;
		}
		
		if (structureChanged)
		{
			int count = getAdjustedLineCount();
			sizes = new SizeSequence(count);
			for (int i = 0; i < count; i++)
			{
				sizes.setSize(i, getLineHeight(i));
			}
			structureChanged = false;
		}
		else
		{
			sizes.setSize(startLine, getLineHeight(startLine));
		}
		
		startLine = -1;
	}
	
	private int getMaxDigits()
	{
		if (maxDigits <= 0)
		{
			int lineCount = getAdjustedLineCount();
			maxDigits = Math.max(MIN_DIGITS, String.valueOf(lineCount).length());
		}
		return maxDigits;
	}
	
	private int getComponentWidth()
	{
		if (componentWidth <= 0)
		{
			componentWidth = getMaxDigits() * digitWidth + MARGIN * 2;
		}
		return componentWidth;
	}
	
	/* Copied from javax.swing.text.PlainDocument */
	private int getAdjustedLineCount()
	{
		// There is an implicit break being modeled at the end of the
		// document to deal with boundary conditions at the end.  This
		// is not desired in the line count, so we detect it and remove
		// its effect if throwing off the count.
		Element map = text.getDocument().getDefaultRootElement();
		int n = map.getElementCount();
		Element lastLine = map.getElement(n - 1);
		if ((lastLine.getEndOffset() - lastLine.getStartOffset()) > 1)
		{
			return n;
		}
		
		return n - 1;
	}
	
	/**
	 * Get the height of a line from the JTextComponent.
	 * 
	 * @param index the line number
	 * @param the height, in pixels
	 */
	private int getLineHeight(int index)
	{
		int lastPos = sizes.getPosition(index) + textTopInset;
		int height = textFontHeight;
		try
		{
			Element map = text.getDocument().getDefaultRootElement();
			int lastChar = map.getElement(index).getEndOffset() - 1;
			Rectangle r = text.modelToView(lastChar);
			height = (r.y - lastPos) + r.height;
		}
		catch (BadLocationException ex)
		{
			ex.printStackTrace();
		}
		return height;
	}
	
	/**
	 * Cache some values that are used a lot in painting or size
	 * calculations. Also ensures that the line-number font is not
	 * larger than the text component's font (by point-size, anyway).
	 */
	private void updateCachedMetrics()
	{
		Font textFont = text.getFont();
		FontMetrics fm = getFontMetrics(textFont);
		textFontHeight = fm.getHeight();
		textFontAscent = fm.getAscent();
		textTopInset = text.getInsets().top;
		
		Font viewFont = getFont();
		boolean changed = false;
		if (viewFont == null)
		{
			viewFont = UIManager.getFont("Label.font");
			changed = true;
		}
		if (viewFont.getSize() > textFont.getSize())
		{
			viewFont = viewFont.deriveFont(textFont.getSize2D());
			changed = true;
		}
		
		viewFontMetrics = getFontMetrics(viewFont);
		digitWidth = viewFontMetrics.stringWidth("0");
		
		if (changed)
		{
			super.setFont(viewFont);
		}
	}
	
	public Dimension getPreferredSize()
	{
		return new Dimension(getComponentWidth(), text.getHeight());
	}
	
	public void setFont(Font font)
	{
		super.setFont(font);
		updateCachedMetrics();
	}
	
	public void paintComponent(Graphics g)
	{
		updateSizes();
		Rectangle clip = g.getClipBounds();
		
		g.setColor(getBackground());
		g.fillRect(clip.x, clip.y, clip.width, clip.height);
		
		g.setColor(getForeground());
		int base = clip.y - textTopInset;
		int first = sizes.getIndex(base);
		int last = sizes.getIndex(base + clip.height);
		String text = "";
		
		for (int i = first; i <= last; i++)
		{
			text = String.valueOf(i+1);
			int x = getComponentWidth() - MARGIN - viewFontMetrics.stringWidth(text);
			int y = sizes.getPosition(i) + textFontAscent + textTopInset;
			g.drawString(text, x, y);
		}
	}
	
	
	class UpdateHandler extends ComponentAdapter
	implements PropertyChangeListener, DocumentListener
	{
		/**
		 * The text component was resized.
		 */
		public void componentResized(ComponentEvent evt)
		{
			viewChanged(0, true);
		}
		
		/**
		 * A bound property was changed on the text component. Properties
		 * like the font, border, and tab size affect the layout of the
		 * whole document, so we invalidate all the line heights here.
		 */
		public void propertyChange(PropertyChangeEvent evt)
		{
			Object oldValue = evt.getOldValue();
			Object newValue = evt.getNewValue();
			String propertyName = evt.getPropertyName();
			if ("document".equals(propertyName))
			{
				if (oldValue != null && oldValue instanceof Document)
				{
					((Document)oldValue).removeDocumentListener(this);
				}
				if (newValue != null && newValue instanceof Document)
				{
					((Document)newValue).addDocumentListener(this);
				}
			}
			
			updateCachedMetrics();
			viewChanged(0, true);
		}
		
		/**
		 * Text was inserted into the document.
		 */
		public void insertUpdate(DocumentEvent evt)
		{
			update(evt);
		}
		
		/**
		 * Text was removed from the document.
		 */
		public void removeUpdate(DocumentEvent evt)
		{
			update(evt);
		}
		
		/**
		 * Text attributes were changed.  In a source-code editor based on
		 * StyledDocument, attribute changes should be applied automatically
		 * in response to inserts and removals.  Since we're already 
		 * listening for those, this method should be redundant, but YMMV.
		 */
		public void changedUpdate(DocumentEvent evt)
		{
			//      update(evt);
		}
		
		/**
		 * If the edit was confined to a single line, invalidate that
		 * line's height.  Otherwise, invalidate them all.
		 */
		private void update(DocumentEvent evt)
		{
			Element map = text.getDocument().getDefaultRootElement();
			int line = map.getElementIndex(evt.getOffset());
			DocumentEvent.ElementChange ec = evt.getChange(map);
			viewChanged(line, ec != null);
		}
	}
	
	public static void main(String[] args) {

		final JFrame frame = new JFrame();
		frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		JTextPane textPane = new JTextPane();

		Date date = new Date();
		String user = System.getProperty("user.name");

		textPane.setText("/**\n * Created On : " + date + "\n * Author : "
				+ user + "\n */\n" + "\npublic class " + "Sample" + " {\n"
				+ "\n\tpublic " + "Sample" + "() {\n\n\n" + "\t}" + "\n}");

		JScrollPane scrollPane = new JScrollPane(textPane);
		scrollPane.setRowHeaderView(new LineNumberView(textPane));
		frame.setContentPane(scrollPane);
		SwingUtilities.invokeLater(new Runnable() {
			public void run() {
				frame.setSize(new Dimension(400, 400));
				frame.setLocationRelativeTo(null);
				frame.setVisible(true);
			}
		});
	}
}
843804
Do you mean the way it shows that final line number (13 in your example) even though the line before it doesn't end in a newline? I don't see that as a problem, myself.

As for setting the font, just call setFont() on it. If you give it a font that's larger than that of the text component, it silently reduces the point size to keep the numbers from overlapping each other. But this works for me:
    JScrollPane scrollPane = new JScrollPane(textPane);
    LineNumberView lnv = new LineNumberView(textPane);
    lnv.setFont(new Font("monospaced", Font.PLAIN, 12));
    scrollPane.setRowHeaderView(lnv);
Some of those settings, like the font, margins and minimum width, really should be configurable. I suppose I could have it look for them in the UIDefaults table under keys like "LineNumberView.font", and only use the hard-coded defaults if the keys aren't present.
843804
What I mean is the code I posted displays some pieces of codes but the line number excides w/ one. Where as I did not added any new line. What do you think I can do about it ? thanks.
843804
I'm still not sure this is what you're talking about, but you can get rid of that extra line number at the end by changing the getAdjustedLineCount() method to the following:
  private int getAdjustedLineCount()
  {
    Element map = text.getDocument().getDefaultRootElement();
    return map.getElementCount() - 1;
  }
It seems to work just fine. In fact, it's probably a good idea to change the method name to simply getLineCount(), to avoid future confusion (I'd rather not post the whole class again).

I don't remember why that method was needed, but apparently the need no longer exists, because I can't find it in the jdk source any more.
843804
Thanks to both of you I really appricaite it, I wish I could split points here :)
843804
Guess I could hehe, thanks again guys :)
843804
You're welcome. Thanks for the dukes.
843804
Got a problem.

Why is it that the line is not wrapped ? I tried doing this :

text4.setLineWrap(true);
text4.setWrapStyleWord(true);

It does not work in JTextPane ? what do we need to do regarding this ?
I also tried casting but the compiler does not allow it :

((JTextArea) text4).setLineWrap(true);
((JTextArea) text4).setWrapStyleWord(true);

Where text4 is JTextPane, thanks for all the help.
843804
Which code are you using? Normally, JTextPanes wrap lines by default.
843804
Yes I'm using JTextPane but I tried adding a bunch of text on the editor and the line was to long so instead of getting a new line number from the gutter so that it can adjust I receive a blank one ?
843804
You mean the line wraps, but you don't see another line number? That's how it's supposed to work.
843804
Well in a normal IDE you should still see a line number, how can I ffo that thanks ?
843804
Use the code that Stas linked to in reply #22. But for a source-code editor, that approach makes no sense. If the line numbers don't match the line numbers given in compiler error messages, what use are they?
843804
I agree with uncle_alice. Most IDE don't wrap lines at all. How do you want to make use of a line number in case of an exception if the line numbering depends on the current editor's size?

The_Developer, where were you when we were discussing "logical lines vs. visible lines"?
843804
weebib, I was actually busy implementing the codes. I actually got the keyword coloring, line highlight and line number to work in my JTextPane.

uncle_alice, maybe thats what I need, to not to wrap the lines :) I was actually a bit confused of wrapping and not wrapping. What you've done is almost perfect however this is the problem.

For example the codes is way behind the current view of the JTextPane. The line number skips one line like this :

http://www.sourceforge.net/project/screenshots.php?group_id=100285&ssid=5319

How can we do it like this :

http://www.sourceforge.net/project/screenshots.php?group_id=100285&ssid=5318

I really appriciate the help . . .
The_Developer
843804
If you see a space on the links that I gave your from the last digit kindly remove it since it will give an error.
843804
Yep, long urls sometimes behave weirdly on these forums.

I don't know if it is what you're looking for but you can easily turn off line wrapping in a JTextPane by inserting it at the CENTER position of a JPanel(new BorderLayout()). You then add the JPanel to the JScrollPane. That will force the horizontal scroll bar to appear (as in any IDE I know).
843804
Actually what I do is to add the JTextPane to a JScrollPane and add it to the JTabbedPane. How would I do what you want ?
843804
I see what you mean but the scroll of my mouse moves slow, is there any way we can fix this or is there any other solution ?
843804
I solved the problem using your comments, thanks a lot to the both of you.
843804
I just wanna follow up on why the scroll on my mouse is slow in scrolling ? when I tried that solution ?
843804
That may be due to the fact that the intermediary JPanel is not explicitely Scrollable (meaning that it doesn't implement the corresponding interface). That would be my advice : create a dummy Scrollable JPanel class and make sure you return the right values in getScrollableUnitIncrement and getScrollableBlockIncrement. You might actually make it simple by returning exactly the values returned by the underlying JTextPane (which is an implementation of Scrollable) so that you don't have to think too much about what to return.
843804
I trying to grasp what you said but I'm way over it, would you mine if you can give me maybe just a small sample ? Thanks.
843804
I trying to grasp what you said but I'm way over it,
would you mine if you can give me maybe just a small
sample ? Thanks.
That'll be too easy :). You won't learn if you expect people to code for you.
- create a class called IntermediaryPanel which extends JPanel and implement Scrollable
- pass the JTextPane in the constructor and store the reference in a member field of your class
- set the layout to BorderLayout
- add the JTextPane in the CENTER location
- implement all methods of Scrollable : for each of these methods just call the exact same method on the JTextPane (return the value when there's is a return value, of course).
- add an instance of this class to as your JScrollPane's viewportview (the argument of the JScrollPane's constructor)

I hope this is simple enough.
843804
About those screenshots:

In the first one, the problem is not with the LineNumberView, but with the line-breaking algorithm used by the Swing text package. It just scans backward through the line and breaks on the first whitespace character it finds. In this case, it's breaking before the first non-whitespace character, with the goofy-looking result you see in the screenshot. The LineNumberView helps a little by letting you know that the line has been broken, but you still have to stop and think a bit before you can tell what's happening.

In the second screenshot, the line has been broken at a more reasonable place, but there are two other problems. One is that there's a line number next to the second visible line, making it look like another logical line. The only indication that it's really a single, soft-wrapped line is the way the second part starts at the left margin, in violation of the overall indenting scheme.

And that's the second problem: when lines are soft-wrapped in a source-code editor, they should also be soft-indented so as not to conflict with the author's indenting scheme. The only editor I've seen that does this is Crimson Editor*. Most IDE's either don't wrap lines at all, or do a half-assed job of it (e.g., using the legacy wrapping behavior from the Swing text package). I find it very odd that so little attention has been paid to this problem, considering that every third person on this forum seems to be writing his/her own editor/IDE (myself included, of course).


(*) http://www.crimsoneditor.com/
1 - 50 Next
Locked Post
New comments cannot be posted to this locked post.

Post Details

Locked on Feb 18 2005
Added on Jan 10 2005
63 comments
2,092 views