5

I am trying to write a JTextPane which supports some sort of coloring: as the user is typing the text, I am running some code that colors the text according to a certain algorithm. This works well.

The problem is that the coloring operations is registered with the undo manager (a DefaultDocumentEvent with EventType.CHANGE). So when the user clicks undo the coloring disappears. Only at the second undo request the text itself is rolled back.

(Note that the coloring algorithm is somewhat slow so I cannot color the text as it is being inserted).

If I try to prevent the CHANGE events from reaching the undo manager I get an exception after several undo requests: this is because the document contents are not conforming to what the undoable-edit object expects.

Any ideas?

Itay Maman
  • 30,277
  • 10
  • 88
  • 118

4 Answers4

1

How are you trying to prevent the CHANGE events from reaching the undo manager?

Can you not send the UndoManager a lastEdit().die() call immediately after the CHANGE is queued?

Richard Campbell
  • 3,591
  • 23
  • 18
1

I can only assume how you are doing the text colouring. If you are doing it in the StyledDocuments change character attribute method you can get the undo listener and temporarily deregister it from the document for that operation and then once the colour change has finshed then you can reregister the listener.

Should be fine for what you are trying to do there.

hope that helps

  • DON'T DO IT LIKE THIS!!! I got a really bad java internal exception trying to do it this way. The complete Swing Thread crashed on me, every repaint threw this Exception: Exception in thread "AWT-EventQueue-0" java.lang.IllegalArgumentException: offset out of bounds at sun.util.locale.provider.RuleBasedBreakIterator.checkOffset(RuleBasedBreakIterator.java:759) ... – Adrodoc Dec 28 '15 at 22:30
1

You could intercept the CHANGE edits and wrap each one in another UndoableEdit whose isSignificant() method returns false, before adding it to the UndoManager. Then each Undo command will undo the most recent INSERT or REMOVE edit, plus every CHANGE edit that occurred since then.

Ultimately, I think you'll find that the styling mechanism provided by JTextPane/StyledDocument/etc. is too limited for this kind of thing. It's slow, it uses too much memory, and it's based on the same Element tree that's used to keep track of the lexical structure of the document. It's okay (I guess) for applications in which the styles are applied by the user, like word processors, but not for a syntax highlighter that has to update the styles constantly as the user types.

There are several examples out there of syntax-highlighting editors based on custom implementations of the Swing JTextComponent, View and Document classes. Some, like JEdit, re-implement practically the whole javax.swing.text package, but I don't think you need to go that far.

Alan Moore
  • 73,866
  • 12
  • 100
  • 156
  • Exending DefaultStyledDocument has caused a lot of headaches for me. I was able to handle the undo/redo colour problem by disabling the undo manager when I did changes to the style. But there are obvious problems as the styled document doesnt really get insertString calls when undo/redo is done - it seems to do stuff directly itself to the model of the document which likely breaks model when it is done in between attributes. It would have been so much easier if it could just call those methods on the DefaultStyledDocument when undo/redo is pressed. – Johncl Feb 08 '20 at 08:20
0

I have just been through this problem. Here is my solution:

private class UndoManagerFix extends UndoManager {

    private static final long serialVersionUID = 5335352180435980549L;

    @Override
    public synchronized void undo() throws CannotUndoException {
        do {
            UndoableEdit edit = editToBeUndone();
            if (edit instanceof AbstractDocument.DefaultDocumentEvent) {
                AbstractDocument.DefaultDocumentEvent event = (AbstractDocument.DefaultDocumentEvent) edit;
                if (event.getType() == EventType.CHANGE) {
                    super.undo();
                    continue;
                }
            }
            break;
        } while (true);

        super.undo();
    }

    @Override
    public synchronized void redo() throws CannotRedoException {
        super.redo();
        int caretPosition = getCaretPosition();

        do {
            UndoableEdit edit = editToBeRedone();
            if (edit instanceof AbstractDocument.DefaultDocumentEvent) {
                AbstractDocument.DefaultDocumentEvent event = (AbstractDocument.DefaultDocumentEvent) edit;
                if (event.getType() == EventType.CHANGE) {
                    super.redo();
                    continue;
                }
            }
            break;
        } while (true);

        setCaretPosition(caretPosition);
    }

}

It is an inner class in my custom JTextPane, so I can fix the caret position on redo.

Vitor
  • 1