Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Get a component from a JTextPane through javax.swing.text.Element?

I am using a JTextPane to display characters and symbols, where the latter are represented by custom painted JComponents. For example, the text pane might show something like this: enter image description here The text pane is user editable and it is allowed for the user to add more symbols via a button at any position and as a replacement for selected text. I do this via the JTextPane.insertComponent() method. At some point in the application I need to know what is currently being displayed in the text pane, and by that I mean not only the entered text, but also the exact components contained within.

I went through extensive troubles with Positions and DocumentListeners to manage the content of my text pane, but I kept causing more problems than I was solving. That is why I finally decided, that my troubles are probably due to a design fault on my part, so I decided to see, if I can't get to my components through the text pane.

Searching through the documentation and the source code of AbstractDocument and other related classes, I found the interface javax.swing.text.Element. I then let my application output

for(int i = 0; i < textPane.getDocument().getLength(); i++) {
    System.out.println(((StyledDocument) textPane.getDocument()).getCharacterElement(i));
}

which gave me:

LeafElement(content) 0,4

LeafElement(content) 0,4

LeafElement(content) 0,4

LeafElement(content) 0,4

LeafElement(component) 4,5

LeafElement(content) 5,9

LeafElement(content) 5,9

LeafElement(content) 5,9

LeafElement(content) 5,9

LeafElement(component) 9,10

Seeing that the LeafElements that I got do seem to have some kind of information about what is displayed at which position in the Document, I figured that it must be possible to get the actual content at that position. After searching for another half hour how to get the content each of the elements represent, I gave up and decided to post my question here, hoping that some of you might know how to accomplish this!?

I have seen this question where someone tries to access the components through textPane.getComponents(), which returns an array of components with the exact number of components actually contained in the JTextPane, but they are all of the type javax.swing.text.ComponentView$Invalidator, which is obviously of no use to me. Maybe I just don't see how to properly continue from here, because a cast to the original type of my symbol doesn't work.

tl;dr

How do I get a JComponent, which is inside the text of a JTextPane, and its position from the text pane?

like image 510
AplusKminus Avatar asked Mar 27 '13 14:03

AplusKminus


2 Answers

You can traverse the text pane's StyledDocument to find elements that represent components or icons, as shown below.

image

BranchElement(section) 0,7

BranchElement(paragraph) 0,7

LeafElement(content) 0,4

LeafElement(icon) 4,5

class javax.swing.plaf.IconUIResource
LeafElement(component) 5,6

class javax.swing.JLabel
LeafElement(content) 6,7

SSCCE:

/**
 * @see http://stackoverflow.com/a/15669307/230513
 * @see http://stackoverflow.com/questions/2883413
 */
public class DocumentParse {

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

    public static void main(String args[]) throws Exception {
        JFrame f = new JFrame();
        f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        JTextPane jtp = new JTextPane();
        StyledDocument doc = (StyledDocument) jtp.getDocument();
        SimpleAttributeSet normal = new SimpleAttributeSet();
        StyleConstants.setFontFamily(normal, "Serif");
        StyleConstants.setFontSize(normal, 72);
        StyleConstants.setForeground(normal, Color.blue);
        doc.insertString(doc.getLength(), "Test", normal);
        jtp.setSelectionStart(doc.getLength());
        jtp.insertIcon(UIManager.getIcon("OptionPane.warningIcon"));
        jtp.setSelectionStart(doc.getLength());
        jtp.insertComponent(new JLabel("Label"));
        jtp.setSelectionStart(doc.getLength());

        ElementIterator iterator = new ElementIterator(doc);
        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());
            }
            if (as.containsAttribute(ELEM, COMP)) {
                System.out.println(StyleConstants.getComponent(as).getClass());
            }
        }

        f.add(jtp);
        f.pack();
        f.setLocationRelativeTo(null);
        f.setVisible(true);
    }
}
like image 140
trashgod Avatar answered Nov 02 '22 23:11

trashgod


The original component is the first (and only) child of the javax.swing.text.ComponentView$Invalidator as you can see from ComponentView.

You can get list of the invalidators and use their children to acccess inserted components.

like image 36
StanislavL Avatar answered Nov 02 '22 22:11

StanislavL