Search code examples
javaswingjtextpane

How to remove the last line/component/icon/widget/etc. from JTextPane


I have a massive Java project which utilizes a console like UI. For the console, I use a JTextPane. Recently I have need for methods to remove the first and last line respectively. The method I have for removing the first line is quite trivial and is as follows

public void removeFirstLine() {
        try {
            Element root = outputArea.getDocument().getDefaultRootElement();
            Element first = root.getElement(0);
            outputArea.getDocument().remove(first.getStartOffset(), first.getEndOffset());
            outputArea.setCaretPosition(outputArea.getDocument().getLength());
        } catch (BadLocationException e) {
            ErrorHandler.handle(e);
        }
    }

My problem, however, comes when I try to remove the very last line (NOT OF TEXT I REPEAT NOT OF TEXT). This line could be anything appendable to a JTextPane such as a custom component, a string of text, an icon, etc. If anyone knows how to remove the last "thing" appended to a JTextPane, I would be ever-grateful to you.

EDIT: add minimal Reproducible example:

import javax.swing.*;
import javax.swing.text.*;

public class Test {
    public static void main(String[] args) {
        new Test();
    }

    private static final String ELEM = AbstractDocument.ElementNameAttribute;
    private static final String ICON = StyleConstants.IconElementName;
    private static final String COMP = StyleConstants.ComponentElementName;
    private JTextPane outputArea;

    Test() {
        try {
            //init pane
            outputArea = new JTextPane();

            //insert component
            JTextField c = new JTextField(20);
            Style cs = outputArea.getStyledDocument().addStyle("name", null);
            StyleConstants.setComponent(cs, c);
            outputArea.getStyledDocument().insertString(outputArea.getStyledDocument().getLength(), "string", cs);

            //new line
            println("");

            //add string
            println("this is a string added to the pane");

            //add image
            outputArea.insertIcon(new ImageIcon("/path/to/image.png"));

            //new line
            println("");

            //before
            printContents();

            //----------------------------
            //call removeLastLine() as many times as needed and it should remove the last added "thing"
            //regardless of the order added (ex: component, text, icon should function the same as text, text, text, component obviously)

            //changes should be reflected here
            printContents();

        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public void removeLastLine() {
        //TODO remove last line of text, last component, last image icon, etc.
    }

    public void println(String Usage) {
        try {
            StyledDocument document = (StyledDocument) outputArea.getDocument();
            document.insertString(document.getLength(), Usage + "\n", null);
            outputArea.setCaretPosition(outputArea.getDocument().getLength());
        }

        catch (Exception e) {
            e.printStackTrace();
        }
    }

    public void printContents() {
        try {
            ElementIterator iterator = new ElementIterator(outputArea.getStyledDocument());
            Element element;
            while ((element = iterator.next()) != null) {
                System.out.println(element);
                AttributeSet as = element.getAttributes();
                if (as.containsAttribute(ELEM, ICON)) {
                    System.out.println(StyleConstants.getIcon(as).getClass());
                }
                else if (as.containsAttribute(ELEM, COMP)) {
                    System.out.println(StyleConstants.getComponent(as).getClass());
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

Solution

  • I solved the issue with the following code which I spent the whole afternoon writing.

     /**
         * Removes the last "thing" addeed to the JTextPane whether it's a component,
         *  icon, or string of multi-llined text.
         *
         *  In more detail, this method figures out what it'll be removing and then determines how many calls
         *   are needed to {@link StringUtil#removeLastLine()}
         */
     public void removeLast() {
            boolean removeTwoLines = false;
    
            LinkedList<Element> elements = new LinkedList<>();
            ElementIterator iterator = new ElementIterator(outputArea.getStyledDocument());
            Element element;
            while ((element = iterator.next()) != null) {
                elements.add(element);
            }
    
            int leafs = 0;
    
            for (Element value : elements)
                if (value.getElementCount() == 0)
                    leafs++;
    
            int passedLeafs = 0;
    
            for (Element value : elements) {
                if (value.getElementCount() == 0) {
                    if (passedLeafs + 3 != leafs) {
                        passedLeafs++;
                        continue;
                    }
    
                    if (value.toString().toLowerCase().contains("icon") || value.toString().toLowerCase().contains("component")) {
                        removeTwoLines = true;
                    }
                }
            }
    
            if (removeTwoLines) {
                removeLastLine();
            }
    
            removeLastLine();
        }
    
        /**
         * Removes the last line added to the linked JTextPane. This could appear to remove nothing,
         *  but really be removing just a newline (line break) character.
         */
        public void removeLastLine() {
            try {
                LinkedList<Element> elements = new LinkedList<>();
                ElementIterator iterator = new ElementIterator(outputArea.getStyledDocument());
                Element element;
                while ((element = iterator.next()) != null) {
                    elements.add(element);
                }
    
                int leafs = 0;
    
                for (Element value : elements)
                    if (value.getElementCount() == 0)
                        leafs++;
    
                int passedLeafs = 0;
    
                for (Element value : elements) {
                    if (value.getElementCount() == 0) {
                        if (passedLeafs + 2 != leafs) {
                            passedLeafs++;
                            continue;
                        }
    
                        outputArea.getStyledDocument().remove(value.getStartOffset(),
                                value.getEndOffset() - value.getStartOffset());
                    }
                }
            } catch (BadLocationException ignored) {}
            catch (Exception e) {
                e.printStackTrace();
            }
        }
    

    The full code can be viewed at the following: https://github.com/NathanCheshire/Cyder/blob/master/src/cyder/utilities/StringUtil.java