Java Tips Weblog

Text Component Line Number

Posted by Rob Camick on May 23, 2009

Over the years I’ve seen many requests in the forums for the ability to display line numbers in a text component. I’ve probably seen just as many solutions as well. I even posted my own solution years ago. It was my first attempt at doing custom painting so I figured now was a good time to revisit that code to see if I could improve on it.

The approach I took was to create a separate component that could be added to a scroll pane along with the related text component. The main reason for this was to allow for horizontal scrolling of the text component, while having the line numbers remain fixed at the left of the scroll pane. It will support a JTextArea or JTextPane. Typically, a single font will be used by the component so the line heights are all the same size. However, in the case of a JTextPane, it will also support the usage of multiple fonts and font sizes.

The result is the TextLineNumber component. The main features of the TextLineNumber are the added support for:

  • wrapped lines – the first line will show the line number and the wrapped lines will show nothing
  • current line number highllighting – the line number for the current line (the line with the caret) will be painted a different color

Sample code for using the TextLineNumber would be as follows:

JTextPane textPane = new JTextPane();
JScrollPane scrollPane = new JScrollPane(textPane);
TextLineNumber tln = new TextLineNumber(textPane);
scrollPane.setRowHeaderView( tln );

Text-Component-Line-Number

TextLineNumber extends JComponent so you can easily customize the foreground, background or border. The font defaults to the related text components font. It can be changed, but you are responsible for ensuring the font size is reasonable. It also supports a couple of methods to allow for further customization:

  • setBorderGap – a convenience method to adjust the left and right insets of the Border, while retaining the outer MatteBorder
  • setCurrentLineForeground – the Color of the current line number
  • setDigitAlignment – align the line numbers to the LEFT, CENTER or RIGHT
  • setMinimumDisplayDigits – controls the minimum width of the component. The width will increase automatically as necessary.
  • setUpdateFont – enables the automatic updating of the Font when the Font of the related text component changes.

Try The Demo

Launch – Using Java™ Web Start (JRE 6 required)

Get The Code

TextLineNumber.java

16 Responses to “Text Component Line Number”

  1. jago said

    Nice :)

    I would like to do something similar with a JTable – show its row numbers, but do not use the first column for it but a separate space left of it.

    Do you have any idea if somebody had done this before or how to do it?

    Thanks!

  2. scphan said

    Is it possible to implement the code folding behavior with your current implementation of the line numbering system?

    • Rob Camick said

      This component can’t do the folding for you, that is complex logic that must be handled by the text component view.

      The getTextLineNumber() method simply returns a string to be painted for the current line. So if you know what lines are “folded”, then you can return a “folded string” to be painted. There is no standard way to implement folding, that I know of, so yes the code would need to be customized. I’ll make that method protected, so you can extend the class to implement folded lines.

  3. Paul said

    Cool component many thanks for this! I used it in our GUI. I got a fix to submit if the font of the associated text component changes the TextLineNumber component needs to update for this in the constructor add this:

    component.addPropertyChangeListener("font",
        new PropertyChangeListener() {
        @Override
        public void propertyChange(PropertyChangeEvent evt) {
            if (evt.getNewValue() instanceof Font) {
                Font newFont = (Font) evt.getNewValue();
                setFont(newFont);
                setPreferredWidth(true);
            }
        }
    });
    
    modify setPreferredWith the following way:
    
    /**
     *  Calculate the width needed to display the maximum line number
     */
    private void setPreferredWidth() {
        setPreferredWidth(false);
    }
    
    /**
     *  Calculate the width needed to display the maximum line number
     */
    private void setPreferredWidth(boolean alwaysUpdate)
    {
        Element root = component.getDocument().getDefaultRootElement();
        int lines = root.getElementCount();
        int digits = Math.max(String.valueOf(lines).length(),
            minimumDisplayDigits);
    
        //  Update sizes when number of digits in the line number changes
    
        if (alwaysUpdate || lastDigits != digits)
        {
            lastDigits = digits;
            FontMetrics fontMetrics = getFontMetrics( getFont() );
            int width = fontMetrics.charWidth( '0' ) * digits;
            Insets insets = getInsets();
            int preferredWidth = insets.left + insets.right + width;
    
            Dimension d = getPreferredSize();
            d.setSize(preferredWidth, HEIGHT);
            setPreferredSize( d );
            setSize( d );
        }
    }
    

    BR, Paul

    • Rob Camick said

      Thanks, I thought about that when originally writting the code. But I ultimately decided to allow you to change the Font manually. That is, you may prefer to use a font size that is smaller than the related text component. Or you may decide to use a different font family. Therefore, using a property change listener would override the manual setting of the Font.

      I suppose I can add some properties that would control whether the Font should automatically be updated or not. Until I get around to doing this the following is a little simpler solution for those that want automatic updating of the Font:

      component.addPropertyChangeListener("font",
        new PropertyChangeListener()
      {
        @Override
        public void propertyChange(PropertyChangeEvent evt)
        {
          if (evt.getNewValue() instanceof Font)
          {
            Font newFont = (Font) evt.getNewValue();
            setFont(newFont);
            lastDigits = 0;
            setPreferredWidth();
          }
        }
      });
      
  4. sgm said

    perfect!
    Just what I need!

  5. Gnubeutel said

    Great Component. You saved me a lot of work ;)

    But i ended up rewriting one part in particular:
    I’m using a JTextPane and might have different fonts, sizes and whatnot in one document. So i occasionally got the same line number twice in a row, because the line heights changed.
    I fixed that by using the line number as the paint loop variable (looking up the line elements Y-position).

    • Rob Camick said

      Yes, I thought about that originally but didn’t think it would be a common requirement. Anyway, I revisted the code and added support for multiple fonts and font sizes.

  6. Bernard said

    Nice code !
    However, I was wondering if there should not be some synchronisation on the text Document in paintComponent(). What if the document is changed under our feet ?

    Best regards,

    Bernard

Leave a Reply

XHTML: You can use these tags: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <pre> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>