I think this is what you want. Some notes:
- Like your example the code uses blank lines to fill up the document. Although in this example the lines are only ever added once when the text component is created.
- The
invokeLater
is used in the EnterAction to add the code to the end of the Event Dispatch Thread. This makes sure that all updates to the Document have been completed and therefore the maximum value of the scrollbar will be correct. - I used a JTextArea because it is easy to control the size of the text area based on the number of blank lines you need to initialize the text area. If you use a JTextPane then you will need to do extra calculations to determine the actual size of the text pane so that the scrollbars work correctly.
- As a bonus I created a simple NavigationFilter which will prevent the user from moving the caret to any of the blank lines at the end of the Document. This feature will also prevent text selection for any of those blank lines using the mouse.
- It is still possible to delete the blank lines. So you may want to create a KeyBinding that does nothing when the "Delete" key is pressed.
- The code to override the width of the frame is just to prevent the horizontal scrollbar from appearing the first time you enter any data in the text pane. You may want to consider to always display the scrollbar, since it will continually be visible after you add the first line of data anyway.
The Code:
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.text.*;
public class NavigationFilterSuffix extends NavigationFilter
{
private int suffixLength;
private Document doc;
public NavigationFilterSuffix(int suffixLength, Document doc)
{
this.suffixLength = suffixLength;
System.out.println(suffixLength);
this.doc = doc;
}
public void setDot(NavigationFilter.FilterBypass fb, int dot, Position.Bias bias)
{
int endOfDocument = doc.getLength() - suffixLength + 1;
fb.setDot(Math.min(dot, endOfDocument), bias);
}
public void moveDot(NavigationFilter.FilterBypass fb, int dot, Position.Bias bias)
{
int endOfDocument = doc.getLength() - suffixLength + 1;
fb.moveDot(Math.min(dot, endOfDocument), bias);
}
public static void main(String args[]) throws Exception
{
final JTextArea terminal = new JTextArea(15, 30);
// add 14 new lines (one less than rows specified above
terminal.setText("\n\n\n\n\n\n\n\n\n\n\n\n\n\n");
terminal.setFont(new Font("Courier new", Font.PLAIN, 12));
terminal.setBackground(Color.black);
terminal.setForeground(Color.white);
terminal.setCaretColor(Color.green);
terminal.setDragEnabled(false);
terminal.setCaretPosition(0);
final JScrollPane scrollPane = new JScrollPane( terminal );
Action enterAction = new EnterAction();
terminal.getInputMap().put( KeyStroke.getKeyStroke("ENTER"), "enter");
terminal.getActionMap().put("enter", enterAction);
terminal.setNavigationFilter(new NavigationFilterSuffix(terminal.getRows(), terminal.getDocument()));
JFrame frame = new JFrame("Navigation Filter Example");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(scrollPane);
frame.pack();
Dimension d = frame.getSize();
d.width += 20;
frame.setSize(d);
frame.setLocationRelativeTo( null );
frame.setVisible(true);
}
static class EnterAction extends AbstractAction
{
public void actionPerformed(ActionEvent ae )
{
try
{
final JTextArea terminal = (JTextArea)ae.getSource();
Document doc = terminal.getDocument();
int car = terminal.getCaretPosition();
doc.insertString(car, "\n", null);
SwingUtilities.invokeLater(new Runnable()
{
public void run()
{
JScrollPane scrollPane = (JScrollPane)SwingUtilities.getAncestorOfClass(JScrollPane.class, terminal);
JScrollBar sb = scrollPane.getVerticalScrollBar();
sb.setValue( sb.getMaximum());
}
});
}
catch (Exception ex)
{
ex.printStackTrace();
}
}
}
}
Good luck.