15

My goal is to implement blue coloring of keywords written by user into JTextPane. This is how my code look like:

private class DocumentHandler implements DocumentListener {

        @Override
        public void changedUpdate(DocumentEvent ev) {
        }

        @Override
        public void insertUpdate(DocumentEvent ev) {
            highlight();
        }

        @Override
        public void removeUpdate(DocumentEvent ev) {
            highlight();
        }

        private void highlight() {
            String code = codePane.getText();
            SimpleAttributeSet defSet = new SimpleAttributeSet();
            StyleConstants.setForeground(defSet, Color.BLACK);
            doc.setCharacterAttributes(0, code.length(), defSet, true);
            SimpleAttributeSet set = new SimpleAttributeSet();
            StyleConstants.setForeground(set, Color.BLUE);
            for (String keyword : keywords) {
                Pattern pattern = Pattern.compile(keyword + "(\\[\\])*");
                Matcher matcher = pattern.matcher(code);
                while (matcher.find()) {

                    //Just for test
                    System.out.print("Start index: " + matcher.start());
                    System.out.print(" End index: " + matcher.end());
                    System.out.println(" Found: " + matcher.group());

                    doc.setCharacterAttributes(matcher.start(), keyword.length(), set, true);
                }
            }
        }
    }

After typing anything into pane I get:

Exception in thread "AWT-EventQueue-0" java.lang.IllegalStateException: Attempt to mutate in notification
    at javax.swing.text.AbstractDocument.writeLock(AbstractDocument.java:1338)
    at javax.swing.text.DefaultStyledDocument.setCharacterAttributes(DefaultStyledDocument.java:500)
    at jnotepad.MainPanel$DocumentHandler.highlight(MainPanel.java:121)
    at jnotepad.MainPanel$DocumentHandler.insertUpdate(MainPanel.java:108)
    at javax.swing.text.AbstractDocument.fireInsertUpdate(AbstractDocument.java:202)
    at javax.swing.text.AbstractDocument.handleInsertString(AbstractDocument.java:749)

How to solve my problem? Maybe I should use something other than DocumentListener?

kleopatra
  • 49,346
  • 26
  • 88
  • 189
user2102972
  • 231
  • 1
  • 6
  • 12

2 Answers2

24

You need to invoke changes to the document from the Event Dispatcher Thread.

Try this:

private void highlight() {

    Runnable doHighlight = new Runnable() {
        @Override
        public void run() {
            // your highlight code
        }
    };       
    SwingUtilities.invokeLater(doHighlight);
}
moeTi
  • 3,704
  • 21
  • 34
  • 8
    Issue is not that `highlight()` is executing from wrong thread. Rather, `invokeLater(Runnable)` fixes problem because it postpones execution until Document lock is released. – Hollis Waite Jan 27 '14 at 15:33
  • @HollisWaite thats what I thought. If unlucky scheduling occurs, this will fail I guess? – Moritz Schmidt Nov 13 '20 at 08:53
  • 1
    @MoritzSchmidt, unlucky scheduling should never occur. Queued EDT tasks run to completion before successors are processed. – Hollis Waite Nov 18 '20 at 04:15
  • @HollisWaite Thats nice to know. Thank you. But the solution still looks like a hack to me.. Or is this really the cleanest solution? – Moritz Schmidt Nov 19 '20 at 08:20
  • 1
    @MoritzSchmidt, I'm no expert on JTextPane internals. I think that's the most elegant solution but I could be wrong. Regardless, if you're building a moderately complex Swing application, you'll inevitably find yourself leveraging the 'invokeLater' idiom. In this particular case, the cleanest option is to use some third-party library (e.g. JSyntaxPane). – Hollis Waite Nov 20 '20 at 13:18
2

I had the same problem, I solved it by using this:

expiration_timeTF.getDocument().addDocumentListener(
            new DocumentListener() {
                @Override
                public void removeUpdate(DocumentEvent e) {
                    System.out.println("remove");
                }

                private void assistDateText() {
                    Runnable doAssist = new Runnable() {
                        @Override
                        public void run() {
                            // when input "2013",will add to "2013-";when
                            // input "2013-10",will add to "2013-10-"
                            String input = expiration_timeTF.getText();
                            if (input.matches("^[0-9]{4}")) {
                                expiration_timeTF.setText(input + "-");
                            } else if (input.matches("^[0-9]{4}-[0-9]{2}")) {
                                expiration_timeTF.setText(input + "-");
                            }
                        }
                    };
                    SwingUtilities.invokeLater(doAssist);
                }

                @Override
                public void insertUpdate(DocumentEvent e) {
                    // System.out.println("insert");
                    assistDateText();
                }

                @Override
                public void changedUpdate(DocumentEvent e) {
                    // System.out.println("change");
                }
            });
Seth
  • 1,490
  • 1
  • 14
  • 28