Search code examples
javaswingjtextarea

How to make the end of a JTextArea editable


Is there a way to make the end of a JTextArea editable and make anything that has already been printed to it not editable?

What I mean by this is if I've written "Hello World" for example to a JTextArea, how could I make it so that the user can type in whatever they want after "Hello World" but they cannot type before that or delete the already printed text?

Below is a small program to demonstrate my troubles...

public class Test {
    public static void main(String[] args) {
        //Here I create a simple JFrame with JTextArea
        JTextArea textArea = new JTextArea();
        JFrame frame = new JFrame();
        JFrame.setDefaultLookAndFeelDecorated(true);
        frame.setSize(250, 250);
        textArea.setEditable(true);
        textArea.setVisible(true);
        frame.add(textArea);
        frame.setVisible(true);
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);


        /*Here I print "Hello World" onto the text area.. after the ">>" I want the
        the user to be able to type whatever they want.. however I don't want them
        to be able to edit the "Hello World"*/
        textArea.append("Hello World\n>>");
        textArea.setCaretPosition(textArea.getDocument().getLength());
    }
}

In the example the user is able to enter whatever text they want.. which is what I want.. however they are also able to edit the text that I printed using append.. which I don't want..

How can I solve this?


Solution

  • Yes, a DocumentFilter will work. Create one that only allows addition of text if the addition is at the end of the document -- that is if the offset equals the document's length. Also totally inactivate the remove method. Something like so:

    import javax.swing.text.AttributeSet;
    import javax.swing.text.BadLocationException;
    import javax.swing.text.DocumentFilter;
    
    public class MyFilter extends DocumentFilter {
        @Override
        public void insertString(FilterBypass fb, int offset, String string, AttributeSet attr)
                throws BadLocationException {
            // only insert text if at the end of the document
            // if offset == document length
            if (offset == fb.getDocument().getLength()) {
                super.insertString(fb, offset, string, attr);
            }
        }
    
        @Override
        public void replace(FilterBypass fb, int offset, int length, String text, AttributeSet attrs)
                throws BadLocationException {
            // only replace text if at the end of the document
            // if offset == document length
            if (offset == fb.getDocument().getLength()) {
                super.replace(fb, offset, length, text, attrs);
            }
        }
    
        @Override
        public void remove(FilterBypass fb, int offset, int length) throws BadLocationException {
            // do nothing. Totally inactivate this
        }
    }
    

    And you could test it like so:

    import javax.swing.*;
    import javax.swing.text.PlainDocument;
    
    @SuppressWarnings("serial")
    public class LimitedTextArea extends JPanel {
        private JTextArea textArea = new JTextArea(15, 50);
    
        public LimitedTextArea() {
            // get textArea's Document and cast to PlainDocument:
            PlainDocument document = (PlainDocument) textArea.getDocument();
            // set the document's filter with "MyFilter"
            document.setDocumentFilter(new MyFilter());
    
            textArea.setLineWrap(true);
            textArea.setWrapStyleWord(true);
            JScrollPane scrollPane = new JScrollPane(textArea);
            scrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);
            add(scrollPane);
        }
    
        private static void createAndShowGui() {
            LimitedTextArea mainPanel = new LimitedTextArea();
    
            JFrame frame = new JFrame("LimitedTextArea");
            frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
            frame.getContentPane().add(mainPanel);
            frame.pack();
            frame.setLocationRelativeTo(null);
            frame.setVisible(true);
        }
    
        public static void main(String[] args) {
            SwingUtilities.invokeLater(() -> createAndShowGui());
        }
    }