001package jahuwaldt.swing.undo;
002
003import java.util.ArrayList;
004import javax.swing.event.UndoableEditEvent;
005import javax.swing.event.UndoableEditListener;
006import javax.swing.undo.UndoManager;
007import javax.swing.undo.UndoableEdit;
008import javax.swing.undo.UndoableEditSupport;
009
010/**
011 * An extension of UndoManager that provides two additional features: (1) The ability to
012 * add & remove listeners and (2) the ability to gain more extensive access to the
013 * edits being managed. See: O'Reilly's Java Swing (1st edition).
014 *
015 * <p> Modified by: Joseph A. Huwaldt, Date: February 22, 2025</p>
016 */
017@SuppressWarnings("serial")
018public class ExtendedUndoManager extends UndoManager implements UndoableEditListener {
019
020    private final ExtendedUndoableEditSupport support = new ExtendedUndoableEditSupport();
021
022    private Object source = this; // The source of the last edit
023
024    /**
025     * Returns the the next significant edit to be undone if undo is called. May return
026     * null.
027     */
028    @Override
029    public UndoableEdit editToBeUndone() {
030        return super.editToBeUndone();
031    }
032
033    /**
034     * Returns the the next significant edit to be redone if redo is called. May return
035     * null.
036     */
037    @Override
038    public UndoableEdit editToBeRedone() {
039        return super.editToBeRedone();
040    }
041
042    /**
043     * Return the complete list of edits in an array.
044     *
045     * @return The complete list of edits in an array.
046     */
047    public synchronized UndoableEdit[] getEdits() {
048        UndoableEdit[] array = new UndoableEdit[edits.size()];
049        edits.copyInto(array);
050        return array;
051    }
052
053    /**
054     * Return all currently significant undoable edits. The first edit is the next one to
055     * be undone.
056     *
057     * @return All currently significant undoable edits.
058     */
059    public synchronized UndoableEdit[] getUndoableEdits() {
060        int size = edits.size();
061        ArrayList<UndoableEdit> v = new ArrayList(size);
062        for (int i = size - 1; i >= 0; i--) {
063            UndoableEdit u = edits.elementAt(i);
064            if (u.canUndo() && u.isSignificant())
065                v.add(u);
066        }
067        UndoableEdit[] array = new UndoableEdit[v.size()];
068        v.toArray(array);
069        return array;
070    }
071
072    /**
073     * Return all currently significant redoable edits. The first edit is the next one to
074     * be redone.
075     *
076     * @return All currently significant redoable edits.
077     */
078    public synchronized UndoableEdit[] getRedoableEdits() {
079        int size = edits.size();
080        ArrayList<UndoableEdit> v = new ArrayList(size);
081        for (int i = 0; i < size; i++) {
082            UndoableEdit u = edits.elementAt(i);
083            if (u.canRedo() && u.isSignificant())
084                v.add(u);
085        }
086        UndoableEdit[] array = new UndoableEdit[v.size()];
087        v.toArray(array);
088        return array;
089    }
090
091    /**
092     * Add an edit and notify our listeners.
093     *
094     * @param anEdit An edit and notify our listeners.
095     */
096    @Override
097    public synchronized boolean addEdit(UndoableEdit anEdit) {
098        boolean b = super.addEdit(anEdit);
099        if (b)
100            support.postEdit(anEdit); // If the edit was added, notify listeners.
101        return b;
102    }
103
104    /**
105     * When an edit is sent to us, call addEdit() to notify any of our listeners.
106     */
107    @Override
108    public synchronized void undoableEditHappened(UndoableEditEvent ev) {
109        UndoableEdit ue = ev.getEdit();
110        source = ev.getSource();
111        addEdit(ue);
112    }
113
114    /**
115     * Add a listener to be notified each time an edit is added to this manager. This
116     * makes it easy to update undo/redo menus as edits are added.
117     *
118     * @param l The listener to be notified each time an edit is added to this manager.
119     */
120    public synchronized void addUndoableEditListener(UndoableEditListener l) {
121        support.addUndoableEditListener(l);
122    }
123
124    /**
125     * Remove a listener from this manager.
126     *
127     * @param l The listener to be notified each time an edit is added to this manager.
128     */
129    public synchronized void removeUndoableEditListener(UndoableEditListener l) {
130        support.removeUndoableEditListener(l);
131    }
132
133    /**
134     * A simple extension of UndoableEditSupport that lets us specify the event source
135     * each time we post an edit
136     */
137    class ExtendedUndoableEditSupport extends UndoableEditSupport {
138
139        // Post an edit to added listeners.
140        @Override
141        public synchronized void postEdit(UndoableEdit ue) {
142            realSource = source; // From our enclosing manager object
143            super.postEdit(ue);
144        }
145    }
146}