5

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?

Community
  • 1
  • 1
AplusKminus
  • 1,542
  • 1
  • 19
  • 32
  • Hopefully, this [answer](http://stackoverflow.com/a/10463120/1057230), might can help in providing some idea regarding the positioning thingy. +1 for more attention :-) – nIcE cOw Mar 27 '13 at 15:11
  • @Gagandeep Bali Thank you for the attention :-). Your answer is interesting, but unfortunately it doesn't apply to my problem, because I don't need to know the `Location` of the component, but rather the position as-in: index within the text. All of that is useless though, if I cannot access the component itself, which is absolutely necessary to my aim. – AplusKminus Mar 27 '13 at 15:25
  • Ahha, this knowledge is beyond my grasp, as yet. Wish someone with wisdom, be able to put light on the topic real soon :-) BEST OF LUCK... – nIcE cOw Mar 27 '13 at 15:31
  • ***[This](http://www.java-forums.org/advanced-java/24786-insert-retrieve-embedded-buttons-jtextpanel.html)*** post might be of help. – Extreme Coders Mar 27 '13 at 16:15
  • @ExtremeCoders That did in fact help. That way I can access all of my components properly. I cannot fully accept this as an answer yet, because of two reasons: 1. you didn't post it as an answer and 2. I cannot get the position of each component this way, or can I? (And by position, I mean not `Location` but rather index.) – AplusKminus Mar 27 '13 at 17:09

2 Answers2

11

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);
    }
}
trashgod
  • 203,806
  • 29
  • 246
  • 1,045
  • Now, THIS is the kind of answer I was looking for! I knew that it had to be possible to access everything in the `JTextPane` through `Elements`, I just couldn't figure out how. Follow up questions: 1. How do I find out that I have to ask the `AttributeSet` for `StyleConstants.ComponentElementName`, I mean how do I know where to look for this kind of string constants? 2. Why does the `ElementIterator` give me a new line character after the document's contents? (I get `LeafElement(content) 12,13` even though `textPane.getDocument.getLength()` yields `12`!) – AplusKminus Mar 28 '13 at 13:52
  • @SheridanVespo: 1. Honestly, I enumerated `getAttributeNames()` and searched the API for the named constants. 2. Courtesy of `AbstractDocument#toString()`. – trashgod Mar 28 '13 at 16:12
  • 1. Thanks, maybe that will help me with future problems I have. 2.`AbstractDocument` does not have a `toString()` method!? At least not according to [grepcode](http://grepcode.com/file/repository.grepcode.com/java/root/jdk/openjdk/7-b147/javax/swing/text/AbstractDocument.java#AbstractDocument). – AplusKminus Mar 28 '13 at 23:31
  • @SheridanVespo: Ah, `AbstractDocument.BranchElement#toString()`; I put a breakpoint in `element.toString()` to see. – trashgod Mar 29 '13 at 01:50
  • But that function does `return "BranchElement(" + getName() + ") " + getStartOffset() + "," + getEndOffset() + "\n";` So, that does imho not explain the additional `LeafElement` at the end!? – AplusKminus Apr 03 '13 at 09:40
  • Oh, I thought you meant the extra _newline_. Note that "`AbstractDocument` models an implied break at the end of the document." – trashgod Apr 03 '13 at 10:46
  • Thank you, that explains it. I meant the extra _new line character_ which is what is appended to the content. – AplusKminus Apr 03 '13 at 15:04
4

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.

StanislavL
  • 56,971
  • 9
  • 68
  • 98
  • See ComponentView's sources. In fact on layout view action setComponentParent is called. It has the code void setComponentParent() { View p = getParent(); if (p != null) { Container parent = getContainer(); if (parent != null) { if (c == null) { // try to build a component Component comp = createComponent(); if (comp != null) { createdC = comp; c = new Invalidator(comp); } } – StanislavL Mar 28 '13 at 10:13