Warning: file_get_contents(/data/phpspider/zhask/data//catemap/9/java/352.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
在jtextarea中显示行号的Java代码_Java_Swing_Jtextarea_Rowheader - Fatal编程技术网

在jtextarea中显示行号的Java代码

在jtextarea中显示行号的Java代码,java,swing,jtextarea,rowheader,Java,Swing,Jtextarea,Rowheader,我试图编写java代码来弹出文本区域,如果我在右侧的jtextarea中添加9000行,它将显示9000行和垂直行 不管怎样,我能做到吗 我是否可以像图片中那样添加行号 请帮帮我!!谢谢 这是我的密码: import java.awt.BorderLayout; import java.awt.FlowLayout; import java.awt.Font; import java.awt.LayoutManager; import java.awt.event.ActionEvent; im

我试图编写java代码来弹出文本区域,如果我在右侧的jtextarea中添加9000行,它将显示9000行和垂直行

不管怎样,我能做到吗

我是否可以像图片中那样添加行号

请帮帮我!!谢谢

这是我的密码:

import java.awt.BorderLayout;
import java.awt.FlowLayout;
import java.awt.Font;
import java.awt.LayoutManager;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

import javax.swing.BoxLayout;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.ScrollPaneConstants;
import javax.swing.text.DefaultCaret;


public class test {

    /**
     * @param args
     */
    public static void main(String[] args) {
        // TODO Auto-generated method stub


        final JFrame frame = new JFrame("Test");
        JPanel panel = new JPanel();
        panel.setLayout((LayoutManager) new BoxLayout(panel, BoxLayout.Y_AXIS));
        panel.setOpaque(true);
        final JTextArea textArea = new JTextArea(20, 30);
        textArea.setWrapStyleWord(true);
        textArea.setEditable(true);
        textArea.setFont(Font.getFont(Font.SANS_SERIF));
        JScrollPane scroller = new JScrollPane(textArea);
        scroller.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS);
        scroller.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);



        JPanel inputpanel = new JPanel();
        inputpanel.setLayout(new FlowLayout());

        JButton button = new JButton("Enter");
        DefaultCaret caret = (DefaultCaret) textArea.getCaret();
        caret.setUpdatePolicy(DefaultCaret.ALWAYS_UPDATE);
        panel.add(scroller);

        inputpanel.add(button);
        panel.add(inputpanel);
        frame.getContentPane().add(BorderLayout.CENTER, panel);
        frame.pack();
        frame.setLocationByPlatform(true);
        frame.setVisible(true);
        frame.setResizable(false);


        button.addActionListener(new ActionListener() {
        public void actionPerformed(ActionEvent e) {

            frame.dispose();
        }});
        frame.setSize(500, 400);
        frame.setVisible(true);
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);   


    }

}

请帮帮我!!谢谢

有关可用作包含文本区域的滚动窗格的行标题的组件,请参阅。

您可能需要类似的内容。从camickr获取链接

将此链接中的类TextLineNumber添加到工作文件夹中: 点击

工人阶级:


这里的代码基于来自的帖子,但已修复,例如,在按住
ENTER
键时,使用有效的字符串连接以避免延迟

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

public class LineNumberingTextArea extends JTextArea
{
    private JTextArea textArea;

    public LineNumberingTextArea(JTextArea textArea)
    {
        this.textArea = textArea;
        setBackground(Color.LIGHT_GRAY);
        setEditable(false);
    }

    public void updateLineNumbers()
    {
        String lineNumbersText = getLineNumbersText();
        setText(lineNumbersText);
    }

    private String getLineNumbersText()
    {
        int caretPosition = textArea.getDocument().getLength();
        Element root = textArea.getDocument().getDefaultRootElement();
        StringBuilder lineNumbersTextBuilder = new StringBuilder();
        lineNumbersTextBuilder.append("1").append(System.lineSeparator());

        for (int elementIndex = 2; elementIndex < root.getElementIndex(caretPosition) + 2; elementIndex++)
        {
            lineNumbersTextBuilder.append(elementIndex).append(System.lineSeparator());
        }

        return lineNumbersTextBuilder.toString();
    }
}
最后,添加一个用于在文档更改时更新行号的:

myTextArea.getDocument().addDocumentListener(new DocumentListener()
{
    @Override
    public void insertUpdate(DocumentEvent documentEvent)
    {
        lineNumberingTextArea.updateLineNumbers();
    }

    @Override
    public void removeUpdate(DocumentEvent documentEvent)
    {
        lineNumberingTextArea.updateLineNumbers();
    }

    @Override
    public void changedUpdate(DocumentEvent documentEvent)
    {
        lineNumberingTextArea.updateLineNumbers();
    }
});

JScrollPane中,垂直滚动条不能与组件TextLineNumber一起正常工作。 为了纠正此问题(以及显示的另一个问题), 我将类TextLineNumber修改为JTextLineNumber。 在这个新类中,文本组件类是JTextPane,但您可以将其更改为JTextComponent。 要使用它,请将JTextLineNumber组件放入位于西部的面板(带有边框布局),文本组件位于中部。 最后将此面板放入滚动窗格中

我希望它能帮助别人

JTextLineNumber

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

/**
 * This class will display line numbers for a related text component. The text     component must use the same line height for each line. TextLineNumber supports wrapped lines and will highlight the line number of the current line in the text component.
 *
 * This class was designed to be used as a component added to the row header of a     JScrollPane.
 */
public class JTextLineNumber extends JPanel implements CaretListener, DocumentListener, PropertyChangeListener
{
 private static final long serialVersionUID = 1L;
public final static float                LEFT     = 0.0f;
public final static float                CENTER = 0.5f;
public final static float                RIGHT  = 1.0f;


// Text component this TextTextLineNumber component is in sync with

private JTextPane            component;

// Properties that can be changed

private boolean                          updateFont;
private int                                  borderGap;
private Color                                currentLineForeground;
private float                                digitAlignment;
private int                                  minimumDisplayDigits;

// Keep history information to reduce the number of times the component
// needs to be repainted

private int                                  lastDigits;
private int                                  lastHeight;
private int                                  lastLine;

private HashMap<String, FontMetrics> fonts;

/**
 * Create a line number component for a text component. This minimum display width will be based on 3 digits.
 *
 * @param component
 *           the related text component
 */
public JTextLineNumber(JTextPane component)
{
    this(component, 3);
}

/**
 * Create a line number component for a text component.
 *
 * @param component
 *           the related text component
 * @param minimumDisplayDigits
 *           the number of digits used to calculate the minimum width of the component
 */
public JTextLineNumber(JTextPane component,
                       int minimumDisplayDigits)
{
    this.component = component;
    setBorder(null);

    //setFont(component.getFont());

    setBorder(null);
    setBorderGap(5);
    setCurrentLineForeground(Color.BLACK);
    setDigitAlignment(RIGHT);
    setMinimumDisplayDigits(minimumDisplayDigits);

    component.getDocument().addDocumentListener(this);
    component.addCaretListener(this);
    component.addPropertyChangeListener("font", this);
}

/**
 * Gets the update font property
 *
 * @return the update font property
 */
public boolean getUpdateFont()
{
    return updateFont;
}

/**
 * Set the update font property. Indicates whether this Font should be updated automatically when the Font of the related text component is changed.
 *
 * @param updateFont
 *           when true update the Font and repaint the line numbers, otherwise just repaint the line numbers.
 */
public void setUpdateFont(boolean updateFont)
{
    this.updateFont = updateFont;
}

/**
 * Gets the border gap
 *
 * @return the border gap in pixels
 */
public int getBorderGap()
{
    return borderGap;
}

/**
 * The border gap is used in calculating the left and right insets of the border. Default value is 5.
 *
 * @param borderGap
 *           the gap in pixels
 */
public void setBorderGap(int borderGap)
{
    this.borderGap = borderGap;
    lastDigits = 0;
    setPreferredWidth();
}

/**
 * Gets the current line rendering Color
 *
 * @return the Color used to render the current line number
 */
public Color getCurrentLineForeground()
{
    return currentLineForeground == null ? getForeground() : currentLineForeground;
}

/**
 * The Color used to render the current line digits. Default is Coolor.RED.
 *
 * @param currentLineForeground
 *           the Color used to render the current line
 */
public void setCurrentLineForeground(Color currentLineForeground)
{
    this.currentLineForeground = currentLineForeground;
}

/**
 * Gets the digit alignment
 *
 * @return the alignment of the painted digits
 */
public float getDigitAlignment()
{
    return digitAlignment;
}

/**
 * Specify the horizontal alignment of the digits within the component. Common values would be:
 * <ul>
 * <li>TextLineNumber.LEFT
 * <li>TextLineNumber.CENTER
 * <li>TextLineNumber.RIGHT (default)
 * </ul>
 * 
 * @param currentLineForeground
 *           the Color used to render the current line
 */
public void setDigitAlignment(float digitAlignment)
{
    this.digitAlignment = digitAlignment > 1.0f ? 1.0f : digitAlignment < 0.0f ? -1.0f : digitAlignment;
}

/**
 * Gets the minimum display digits
 *
 * @return the minimum display digits
 */
public int getMinimumDisplayDigits()
{
    return minimumDisplayDigits;
}

/**
 * Specify the mimimum number of digits used to calculate the preferred width of the component. Default is 3.
 *
 * @param minimumDisplayDigits
 *           the number digits used in the preferred width calculation
 */
public void setMinimumDisplayDigits(int minimumDisplayDigits)
{
    this.minimumDisplayDigits = minimumDisplayDigits;
    setPreferredWidth();
}

/**
 * Calculate the width needed to display the maximum line number
 */
private void setPreferredWidth()
{
    // Define the font to display the numbers.
    Font componentFont = component.getFont();
    Font font = new Font(componentFont.getFamily(), Font.BOLD, componentFont.getSize());

    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 (lastDigits != digits)
    {
        lastDigits = digits;
        FontMetrics fontMetrics = getFontMetrics(font);
        int iPreferredWidth = 0;

        if (digits <= 1)
        {
            iPreferredWidth = 10 + fontMetrics.stringWidth("0");
        } else if (digits == 2)
        {
            iPreferredWidth = 10 + fontMetrics.stringWidth("00");
        } else if (digits == 3)
        {
            iPreferredWidth = 10 + fontMetrics.stringWidth("000");
        } else if (digits == 4)
        {
            iPreferredWidth = 10 + fontMetrics.stringWidth("0000");
        } else if (digits == 5)
        {
            iPreferredWidth = 10 + fontMetrics.stringWidth("00000");
        } else if (digits == 6)
        {
            iPreferredWidth = 10 + fontMetrics.stringWidth("000000");
        } else if (digits == 7)
        {
            iPreferredWidth = 10 + fontMetrics.stringWidth("0000000");
        } else 
        {
            iPreferredWidth = 10 + fontMetrics.stringWidth("00000000");
        }

        Dimension dimension = new Dimension(iPreferredWidth, 0);
        setPreferredSize(dimension);
        setSize(dimension);
    }
}

/**
 * Draw the line numbers
 */
@Override
public void paintComponent(Graphics g)
{
    // Paint the background.
    Graphics2D g2d = (Graphics2D)g;
    g2d.setColor(component.getBackground());
    g2d.fillRect(0, 0, getWidth(), getHeight());
    // Paint a vertical line at right.
    g2d.setStroke(new BasicStroke(1));
    g2d.setColor(new Color(0, 0, 0, 64));
    g2d.drawLine(getWidth() - 1, 0, getWidth() - 1, getHeight());
    // Define the font.
    Font componentFont = component.getFont();
    Font font = new Font(componentFont.getFamily(), Font.BOLD, componentFont.getSize());

    // Determine the width of the space available to draw the line number

    FontMetrics fontMetrics = component.getFontMetrics(component.getFont());
    int iRightAlignement = getSize().width - 5;

    // Determine the rows to draw within the clipped bounds.

    Rectangle clip = g.getClipBounds();
    int rowStartOffset = component.viewToModel(new Point(0, clip.y));
    int endOffset = component.viewToModel(new Point(0, clip.y + clip.height));
    g2d.setFont(font);

    while (rowStartOffset <= endOffset)
    {
        try
        {
            if (isCurrentLine(rowStartOffset))
                g2d.setColor(new Color(0, 0, 0, 160));
            else
                g2d.setColor(new Color(0, 0, 0, 100));

            // Get the line number as a string and then determine the
            // "X" and "Y" offsets for drawing the string.

            String lineNumber = getTextLineNumber(rowStartOffset);
            int stringWidth = ((Graphics2D)g).getFontMetrics().stringWidth(lineNumber);
            int iX = iRightAlignement - stringWidth;
            int iY = getOffsetY(rowStartOffset, fontMetrics);
            g2d.drawString(lineNumber, iX, iY);

            // Move to the next row

            rowStartOffset = Utilities.getRowEnd(component, rowStartOffset) + 1;
        } catch (Exception e)
        {
            break;
        }
    }
}

/*
 * We need to know if the caret is currently positioned on the line we are about to paint so the line number can be highlighted.
 */
private boolean isCurrentLine(int rowStartOffset)
{
    int caretPosition = component.getCaretPosition();
    Element root = component.getDocument().getDefaultRootElement();

    if (root.getElementIndex(rowStartOffset) == root.getElementIndex(caretPosition))
        return true;
    else
        return false;
}

/*
 * Get the line number to be drawn. The empty string will be returned when a line of text has wrapped.
 */
protected String getTextLineNumber(int rowStartOffset)
{
    Element root = component.getDocument().getDefaultRootElement();
    int index = root.getElementIndex(rowStartOffset);
    Element line = root.getElement(index);

    if (line.getStartOffset() == rowStartOffset)
        return String.valueOf(index + 1);
    else
        return "";
}

/*
 * Determine the Y offset for the current row
 */
private int getOffsetY(int rowStartOffset,
                       FontMetrics fontMetrics)
         throws BadLocationException
{
    // Get the bounding rectangle of the row

    Rectangle r = component.modelToView(rowStartOffset);
    int lineHeight = fontMetrics.getHeight();
    int y = r.y + r.height;
    int descent = 0;

    // The text needs to be positioned above the bottom of the bounding
    // rectangle based on the descent of the font(s) contained on the row.

    if (r.height == lineHeight) // default font is being used
    {
        descent = fontMetrics.getDescent();
    } else // We need to check all the attributes for font changes
    {
        if (fonts == null)
            fonts = new HashMap<String, FontMetrics>();

        Element root = component.getDocument().getDefaultRootElement();
        int index = root.getElementIndex(rowStartOffset);
        Element line = root.getElement(index);

        for (int i = 0; i < line.getElementCount(); i++)
        {
            Element child = line.getElement(i);
            AttributeSet as = child.getAttributes();
            String fontFamily = (String) as.getAttribute(StyleConstants.FontFamily);
            Integer fontSize = (Integer) as.getAttribute(StyleConstants.FontSize);
            String key = fontFamily + fontSize;

            FontMetrics fm = fonts.get(key);

            if (fm == null)
            {
                Font font = new Font(fontFamily, Font.PLAIN, fontSize);
                fm = component.getFontMetrics(font);
                fonts.put(key, fm);
            }

            descent = Math.max(descent, fm.getDescent());
        }
    }

    return y - descent;
}

//
// Implement CaretListener interface
//
@Override
public void caretUpdate(CaretEvent e)
{
    // Get the line the caret is positioned on

    int caretPosition = component.getCaretPosition();
    Element root = component.getDocument().getDefaultRootElement();
    int currentLine = root.getElementIndex(caretPosition);

    // Need to repaint so the correct line number can be highlighted

    if (lastLine != currentLine)
    {
        repaint();
        lastLine = currentLine;
    }
}

//
// Implement DocumentListener interface
//
@Override
public void changedUpdate(DocumentEvent e)
{
    documentChanged();
}

@Override
public void insertUpdate(DocumentEvent e)
{
    documentChanged();
}

@Override
public void removeUpdate(DocumentEvent e)
{
    documentChanged();
}

/*
 * A document change may affect the number of displayed lines of text. Therefore the lines numbers will also change.
 */
private void documentChanged()
{
    // View of the component has not been updated at the time
    // the DocumentEvent is fired

    SwingUtilities.invokeLater(new Runnable()
    {
        @Override
        public void run()
        {
            try
            {
                int endPos = component.getDocument().getLength();
                Rectangle rect = component.modelToView(endPos);

                if (rect != null && rect.y != lastHeight)
                {
                    setPreferredWidth();
                    repaint();
                    lastHeight = rect.y;
                }
            } catch (BadLocationException ex)
            {
                /* nothing to do */ }
        }
    });
}

//
// Implement PropertyChangeListener interface
//
@Override
public void propertyChange(PropertyChangeEvent evt)
{
    if (evt.getNewValue() instanceof Font)
    {
        if (updateFont)
        {
            Font newFont = (Font) evt.getNewValue();
            setFont(newFont);
            lastDigits = 0;
            setPreferredWidth();
        } else
        {
            repaint();
        }
    }
}
}
import java.awt.*;
导入java.beans。*;
导入java.util.HashMap;
导入javax.swing.*;
导入javax.swing.event.*;
导入javax.swing.text.*;
/**
*此类将显示相关文本组件的行号。文本组件必须为每行使用相同的行高。TextLineNumber支持换行,并将高亮显示文本组件中当前行的行号。
*
*此类被设计为用作添加到JScrollPane行标题的组件。
*/
公共类JTextLineNumber扩展了JPanel实现了CaretListener、DocumentListener、PropertyChangeListener
{
私有静态最终长serialVersionUID=1L;
公共最终静态浮动左=0.0f;
公共最终静态浮动中心=0.5f;
公共最终静态浮动权=1.0f;
//文本组件此TextLineNumber组件与同步
私有JTextPane组件;
//可以更改的属性
私有布尔更新;
私营部门之间的差距;
个人色彩前景;
私人浮动数字信号;
私有int最小显示位数;
//保留历史记录信息以减少组件运行的次数
//需要重新油漆
私有整数;
私家车高度;
私人电话线;
私有HashMap字体;
/**
*为文本组件创建行号组件。此最小显示宽度将基于3位数字。
*
*@param组件
*相关文本组件
*/
公共JTextLineNumber(JTextPane组件)
{
这(第3部分);
}
/**
*为文本组件创建行号组件。
*
*@param组件
*相关文本组件
*@param minimumDisplayDigits
*用于计算组件最小宽度的位数
*/
公共JTextLineNumber(JTextPane组件,
int最小显示位数)
{
这个组件=组件;
订单号(空);
//setFont(component.getFont());
订单号(空);
二级缺口(5);
setCurrentLineForeground(颜色为黑色);
设置数字信号(右);
设置最小显示位数(最小显示位数);
component.getDocument().addDocumentListener(此);
component.addCaretListener(本);
addPropertyChangeListener(“字体”,this);
}
/**
*获取更新字体属性
*
*@返回更新字体属性
*/
公共布尔getUpdateFont()
{
返回updateFont;
}
/**
*设置更新字体属性。指示当相关文本组件的字体发生更改时,是否应自动更新此字体。
*
*@param updateFont
*如果为true,则更新字体并重新绘制行号,否则只需重新绘制行号即可。
*/
public void setUpdateFont(布尔更新字体)
{
this.updateFont=updateFont;
}
/**
*获取边界间隙
*
*@返回以像素为单位的边框间距
*/
public int getBorderGap()
{
返回边界间隙;
}
/**
*边框间距用于计算边框的左侧和右侧插入。默认值为5。
*
*@param-borderGap
*像素间距
*/
公共边界间隙(int-borderGap)
{
this.borderGap=borderGap;
最后位数=0;
setPreferredWidth();
}
/**
*获取当前的线渲染颜色
*
*@返回用于呈现当前行号的颜色
*/
公共颜色getCurrentLineForeground()
{
返回currentLineForeground==null?getForeground():currentLineForeground;
}
/**
*用于呈现当前行数字的颜色。默认值为Coolor.RED。
*
*@param currentLineForeground
*用于渲染当前行的颜色
*/
公共无效设置currentLineForeground(颜色currentLineForeground)
{
this.currentLineForeground=currentLineForeground;
}
/**
*获取数字对齐方式
*
*@返回绘制的数字的对齐方式
*/
公共浮点数getDigitAlignment()
{
返回数字信号;
}
/**
*指定组件中数字的水平对齐方式。常用值为:
*
    *
  • TextLineNumber.LEFT *
  • TextLineNumber.CENTER
    myTextArea.getDocument().addDocumentListener(new DocumentListener()
    {
        @Override
        public void insertUpdate(DocumentEvent documentEvent)
        {
            lineNumberingTextArea.updateLineNumbers();
        }
    
        @Override
        public void removeUpdate(DocumentEvent documentEvent)
        {
            lineNumberingTextArea.updateLineNumbers();
        }
    
        @Override
        public void changedUpdate(DocumentEvent documentEvent)
        {
            lineNumberingTextArea.updateLineNumbers();
        }
    });
    
    import java.awt.*;
    import java.beans.*;
    import java.util.HashMap;
    import javax.swing.*;
    import javax.swing.event.*;
    import javax.swing.text.*;
    
    /**
     * This class will display line numbers for a related text component. The text     component must use the same line height for each line. TextLineNumber supports wrapped lines and will highlight the line number of the current line in the text component.
     *
     * This class was designed to be used as a component added to the row header of a     JScrollPane.
     */
    public class JTextLineNumber extends JPanel implements CaretListener, DocumentListener, PropertyChangeListener
    {
     private static final long serialVersionUID = 1L;
    public final static float                LEFT     = 0.0f;
    public final static float                CENTER = 0.5f;
    public final static float                RIGHT  = 1.0f;
    
    
    // Text component this TextTextLineNumber component is in sync with
    
    private JTextPane            component;
    
    // Properties that can be changed
    
    private boolean                          updateFont;
    private int                                  borderGap;
    private Color                                currentLineForeground;
    private float                                digitAlignment;
    private int                                  minimumDisplayDigits;
    
    // Keep history information to reduce the number of times the component
    // needs to be repainted
    
    private int                                  lastDigits;
    private int                                  lastHeight;
    private int                                  lastLine;
    
    private HashMap<String, FontMetrics> fonts;
    
    /**
     * Create a line number component for a text component. This minimum display width will be based on 3 digits.
     *
     * @param component
     *           the related text component
     */
    public JTextLineNumber(JTextPane component)
    {
        this(component, 3);
    }
    
    /**
     * Create a line number component for a text component.
     *
     * @param component
     *           the related text component
     * @param minimumDisplayDigits
     *           the number of digits used to calculate the minimum width of the component
     */
    public JTextLineNumber(JTextPane component,
                           int minimumDisplayDigits)
    {
        this.component = component;
        setBorder(null);
    
        //setFont(component.getFont());
    
        setBorder(null);
        setBorderGap(5);
        setCurrentLineForeground(Color.BLACK);
        setDigitAlignment(RIGHT);
        setMinimumDisplayDigits(minimumDisplayDigits);
    
        component.getDocument().addDocumentListener(this);
        component.addCaretListener(this);
        component.addPropertyChangeListener("font", this);
    }
    
    /**
     * Gets the update font property
     *
     * @return the update font property
     */
    public boolean getUpdateFont()
    {
        return updateFont;
    }
    
    /**
     * Set the update font property. Indicates whether this Font should be updated automatically when the Font of the related text component is changed.
     *
     * @param updateFont
     *           when true update the Font and repaint the line numbers, otherwise just repaint the line numbers.
     */
    public void setUpdateFont(boolean updateFont)
    {
        this.updateFont = updateFont;
    }
    
    /**
     * Gets the border gap
     *
     * @return the border gap in pixels
     */
    public int getBorderGap()
    {
        return borderGap;
    }
    
    /**
     * The border gap is used in calculating the left and right insets of the border. Default value is 5.
     *
     * @param borderGap
     *           the gap in pixels
     */
    public void setBorderGap(int borderGap)
    {
        this.borderGap = borderGap;
        lastDigits = 0;
        setPreferredWidth();
    }
    
    /**
     * Gets the current line rendering Color
     *
     * @return the Color used to render the current line number
     */
    public Color getCurrentLineForeground()
    {
        return currentLineForeground == null ? getForeground() : currentLineForeground;
    }
    
    /**
     * The Color used to render the current line digits. Default is Coolor.RED.
     *
     * @param currentLineForeground
     *           the Color used to render the current line
     */
    public void setCurrentLineForeground(Color currentLineForeground)
    {
        this.currentLineForeground = currentLineForeground;
    }
    
    /**
     * Gets the digit alignment
     *
     * @return the alignment of the painted digits
     */
    public float getDigitAlignment()
    {
        return digitAlignment;
    }
    
    /**
     * Specify the horizontal alignment of the digits within the component. Common values would be:
     * <ul>
     * <li>TextLineNumber.LEFT
     * <li>TextLineNumber.CENTER
     * <li>TextLineNumber.RIGHT (default)
     * </ul>
     * 
     * @param currentLineForeground
     *           the Color used to render the current line
     */
    public void setDigitAlignment(float digitAlignment)
    {
        this.digitAlignment = digitAlignment > 1.0f ? 1.0f : digitAlignment < 0.0f ? -1.0f : digitAlignment;
    }
    
    /**
     * Gets the minimum display digits
     *
     * @return the minimum display digits
     */
    public int getMinimumDisplayDigits()
    {
        return minimumDisplayDigits;
    }
    
    /**
     * Specify the mimimum number of digits used to calculate the preferred width of the component. Default is 3.
     *
     * @param minimumDisplayDigits
     *           the number digits used in the preferred width calculation
     */
    public void setMinimumDisplayDigits(int minimumDisplayDigits)
    {
        this.minimumDisplayDigits = minimumDisplayDigits;
        setPreferredWidth();
    }
    
    /**
     * Calculate the width needed to display the maximum line number
     */
    private void setPreferredWidth()
    {
        // Define the font to display the numbers.
        Font componentFont = component.getFont();
        Font font = new Font(componentFont.getFamily(), Font.BOLD, componentFont.getSize());
    
        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 (lastDigits != digits)
        {
            lastDigits = digits;
            FontMetrics fontMetrics = getFontMetrics(font);
            int iPreferredWidth = 0;
    
            if (digits <= 1)
            {
                iPreferredWidth = 10 + fontMetrics.stringWidth("0");
            } else if (digits == 2)
            {
                iPreferredWidth = 10 + fontMetrics.stringWidth("00");
            } else if (digits == 3)
            {
                iPreferredWidth = 10 + fontMetrics.stringWidth("000");
            } else if (digits == 4)
            {
                iPreferredWidth = 10 + fontMetrics.stringWidth("0000");
            } else if (digits == 5)
            {
                iPreferredWidth = 10 + fontMetrics.stringWidth("00000");
            } else if (digits == 6)
            {
                iPreferredWidth = 10 + fontMetrics.stringWidth("000000");
            } else if (digits == 7)
            {
                iPreferredWidth = 10 + fontMetrics.stringWidth("0000000");
            } else 
            {
                iPreferredWidth = 10 + fontMetrics.stringWidth("00000000");
            }
    
            Dimension dimension = new Dimension(iPreferredWidth, 0);
            setPreferredSize(dimension);
            setSize(dimension);
        }
    }
    
    /**
     * Draw the line numbers
     */
    @Override
    public void paintComponent(Graphics g)
    {
        // Paint the background.
        Graphics2D g2d = (Graphics2D)g;
        g2d.setColor(component.getBackground());
        g2d.fillRect(0, 0, getWidth(), getHeight());
        // Paint a vertical line at right.
        g2d.setStroke(new BasicStroke(1));
        g2d.setColor(new Color(0, 0, 0, 64));
        g2d.drawLine(getWidth() - 1, 0, getWidth() - 1, getHeight());
        // Define the font.
        Font componentFont = component.getFont();
        Font font = new Font(componentFont.getFamily(), Font.BOLD, componentFont.getSize());
    
        // Determine the width of the space available to draw the line number
    
        FontMetrics fontMetrics = component.getFontMetrics(component.getFont());
        int iRightAlignement = getSize().width - 5;
    
        // Determine the rows to draw within the clipped bounds.
    
        Rectangle clip = g.getClipBounds();
        int rowStartOffset = component.viewToModel(new Point(0, clip.y));
        int endOffset = component.viewToModel(new Point(0, clip.y + clip.height));
        g2d.setFont(font);
    
        while (rowStartOffset <= endOffset)
        {
            try
            {
                if (isCurrentLine(rowStartOffset))
                    g2d.setColor(new Color(0, 0, 0, 160));
                else
                    g2d.setColor(new Color(0, 0, 0, 100));
    
                // Get the line number as a string and then determine the
                // "X" and "Y" offsets for drawing the string.
    
                String lineNumber = getTextLineNumber(rowStartOffset);
                int stringWidth = ((Graphics2D)g).getFontMetrics().stringWidth(lineNumber);
                int iX = iRightAlignement - stringWidth;
                int iY = getOffsetY(rowStartOffset, fontMetrics);
                g2d.drawString(lineNumber, iX, iY);
    
                // Move to the next row
    
                rowStartOffset = Utilities.getRowEnd(component, rowStartOffset) + 1;
            } catch (Exception e)
            {
                break;
            }
        }
    }
    
    /*
     * We need to know if the caret is currently positioned on the line we are about to paint so the line number can be highlighted.
     */
    private boolean isCurrentLine(int rowStartOffset)
    {
        int caretPosition = component.getCaretPosition();
        Element root = component.getDocument().getDefaultRootElement();
    
        if (root.getElementIndex(rowStartOffset) == root.getElementIndex(caretPosition))
            return true;
        else
            return false;
    }
    
    /*
     * Get the line number to be drawn. The empty string will be returned when a line of text has wrapped.
     */
    protected String getTextLineNumber(int rowStartOffset)
    {
        Element root = component.getDocument().getDefaultRootElement();
        int index = root.getElementIndex(rowStartOffset);
        Element line = root.getElement(index);
    
        if (line.getStartOffset() == rowStartOffset)
            return String.valueOf(index + 1);
        else
            return "";
    }
    
    /*
     * Determine the Y offset for the current row
     */
    private int getOffsetY(int rowStartOffset,
                           FontMetrics fontMetrics)
             throws BadLocationException
    {
        // Get the bounding rectangle of the row
    
        Rectangle r = component.modelToView(rowStartOffset);
        int lineHeight = fontMetrics.getHeight();
        int y = r.y + r.height;
        int descent = 0;
    
        // The text needs to be positioned above the bottom of the bounding
        // rectangle based on the descent of the font(s) contained on the row.
    
        if (r.height == lineHeight) // default font is being used
        {
            descent = fontMetrics.getDescent();
        } else // We need to check all the attributes for font changes
        {
            if (fonts == null)
                fonts = new HashMap<String, FontMetrics>();
    
            Element root = component.getDocument().getDefaultRootElement();
            int index = root.getElementIndex(rowStartOffset);
            Element line = root.getElement(index);
    
            for (int i = 0; i < line.getElementCount(); i++)
            {
                Element child = line.getElement(i);
                AttributeSet as = child.getAttributes();
                String fontFamily = (String) as.getAttribute(StyleConstants.FontFamily);
                Integer fontSize = (Integer) as.getAttribute(StyleConstants.FontSize);
                String key = fontFamily + fontSize;
    
                FontMetrics fm = fonts.get(key);
    
                if (fm == null)
                {
                    Font font = new Font(fontFamily, Font.PLAIN, fontSize);
                    fm = component.getFontMetrics(font);
                    fonts.put(key, fm);
                }
    
                descent = Math.max(descent, fm.getDescent());
            }
        }
    
        return y - descent;
    }
    
    //
    // Implement CaretListener interface
    //
    @Override
    public void caretUpdate(CaretEvent e)
    {
        // Get the line the caret is positioned on
    
        int caretPosition = component.getCaretPosition();
        Element root = component.getDocument().getDefaultRootElement();
        int currentLine = root.getElementIndex(caretPosition);
    
        // Need to repaint so the correct line number can be highlighted
    
        if (lastLine != currentLine)
        {
            repaint();
            lastLine = currentLine;
        }
    }
    
    //
    // Implement DocumentListener interface
    //
    @Override
    public void changedUpdate(DocumentEvent e)
    {
        documentChanged();
    }
    
    @Override
    public void insertUpdate(DocumentEvent e)
    {
        documentChanged();
    }
    
    @Override
    public void removeUpdate(DocumentEvent e)
    {
        documentChanged();
    }
    
    /*
     * A document change may affect the number of displayed lines of text. Therefore the lines numbers will also change.
     */
    private void documentChanged()
    {
        // View of the component has not been updated at the time
        // the DocumentEvent is fired
    
        SwingUtilities.invokeLater(new Runnable()
        {
            @Override
            public void run()
            {
                try
                {
                    int endPos = component.getDocument().getLength();
                    Rectangle rect = component.modelToView(endPos);
    
                    if (rect != null && rect.y != lastHeight)
                    {
                        setPreferredWidth();
                        repaint();
                        lastHeight = rect.y;
                    }
                } catch (BadLocationException ex)
                {
                    /* nothing to do */ }
            }
        });
    }
    
    //
    // Implement PropertyChangeListener interface
    //
    @Override
    public void propertyChange(PropertyChangeEvent evt)
    {
        if (evt.getNewValue() instanceof Font)
        {
            if (updateFont)
            {
                Font newFont = (Font) evt.getNewValue();
                setFont(newFont);
                lastDigits = 0;
                setPreferredWidth();
            } else
            {
                repaint();
            }
        }
    }
    }