001/* UndoListener helper
002
003 Copyright (c) 2008-2016 The Regents of the University of California.
004 All rights reserved.
005 Permission is hereby granted, without written agreement and without
006 license or royalty fees, to use, copy, modify, and distribute this
007 software and its documentation for any purpose, provided that the above
008 copyright notice and the following two paragraphs appear in all copies
009 of this software.
010
011 IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY
012 FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES
013 ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF
014 THE UNIVERSITY OF CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF
015 SUCH DAMAGE.
016
017 THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES,
018 INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
019 MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE
020 PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, AND THE UNIVERSITY OF
021 CALIFORNIA HAS NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES,
022 ENHANCEMENTS, OR MODIFICATIONS.
023
024 PT_COPYRIGHT_VERSION_2
025 COPYRIGHTENDKEY
026
027 */
028package ptolemy.gui;
029
030import java.awt.Toolkit;
031import java.awt.event.ActionEvent;
032import java.awt.event.KeyEvent;
033
034import javax.swing.AbstractAction;
035import javax.swing.ActionMap;
036import javax.swing.InputMap;
037import javax.swing.KeyStroke;
038import javax.swing.event.UndoableEditEvent;
039import javax.swing.event.UndoableEditListener;
040import javax.swing.text.JTextComponent;
041import javax.swing.undo.CannotRedoException;
042import javax.swing.undo.CannotUndoException;
043import javax.swing.undo.CompoundEdit;
044import javax.swing.undo.UndoManager;
045
046/**
047 * An Undo/Redo listener for use with a JTextComponent.
048 *
049 * <p> A convenience constructor has been provided such that a caller
050 * can immediately have default shortcut key mappings for undo/redo
051 * actions on the text component
052 *
053 * @author Ben Leinfelder
054 * @version $Id$
055 * @since Ptolemy II 8.0
056 * @Pt.ProposedRating Yellow (cxh)
057 * @Pt.AcceptedRating Red (cx)
058 * @author ben leinfelder
059 */
060public class UndoListener implements UndoableEditListener {
061
062    /**
063     * Construct an undo listener.
064     * <p>This constructor allows simple usage without setting up key mapping
065     * automatically (it is then the responsibilty of the caller to
066     * provide a mechanism for invoking the undo and redo actions.)
067     */
068    public UndoListener() {
069    }
070
071    /**
072     * Construct an undo listener with default key mappings.
073     * The default key mappings invoke the undo and redo actions on
074     * <i>textArea</i> .
075     * <p>A typical usage pattern would be:
076     * <code>
077     *         JTextArea textArea = new JTextArea("testing");
078     *         textArea.getDocument().addUndoableEditListener(new UndoListener(textArea));
079     * </code>
080     * @param textArea the text component that is being listened to
081     * and upon which undo/redo actions will be performed
082     */
083    public UndoListener(JTextComponent textArea) {
084
085        // Set the mapping for shortcut keys;
086        InputMap inputMap = textArea.getInputMap();
087        ActionMap actionMap = textArea.getActionMap();
088
089        // Ctrl-z or equivalent to undo.
090        inputMap.put(
091                KeyStroke.getKeyStroke(KeyEvent.VK_Z,
092                        Toolkit.getDefaultToolkit().getMenuShortcutKeyMask()),
093                "undo");
094        actionMap.put("undo", _undoAction);
095        // Ctrl-y or equivalent to redo
096        inputMap.put(
097                KeyStroke.getKeyStroke(KeyEvent.VK_Y,
098                        Toolkit.getDefaultToolkit().getMenuShortcutKeyMask()),
099                "redo");
100        actionMap.put("redo", _redoAction);
101    }
102
103    /** End a compound edit. */
104    public synchronized void endCompoundEdit() {
105        if (_compoundEdit != null) {
106            _compoundEdit.end();
107            _undo.addEdit(_compoundEdit);
108            _undoAction._updateUndoState();
109            _redoAction._updateRedoState();
110            _compoundEdit = null;
111        }
112    }
113
114    /** Perform a redo.
115     *  @exception CannotUndoException  Thrown if the redo
116     *  cannot be done.
117     */
118    public synchronized void redo() throws CannotUndoException {
119        _undo.redo();
120    }
121
122    /** Start a compound undo edit. */
123    public synchronized void startCompoundEdit() {
124        _compoundEdit = new CompoundEdit();
125    }
126
127    /** Perform an undo.
128     *  @exception CannotUndoException  Thrown if the redo
129     *  cannot be done.
130     */
131    public synchronized void undo() throws CannotUndoException {
132        _undo.undo();
133    }
134
135    /** Remember the edit and update the action state.
136     *  @param event The event that occurred.
137     */
138    @Override
139    public synchronized void undoableEditHappened(UndoableEditEvent event) {
140        if (_compoundEdit == null) {
141            _undo.addEdit(event.getEdit());
142            _undoAction._updateUndoState();
143            _redoAction._updateRedoState();
144        } else {
145            _compoundEdit.addEdit(event.getEdit());
146        }
147    }
148
149    ///////////////////////////////////////////////////////////////////
150    ////                         protected variables               ////
151
152    /** The redo action. */
153    protected RedoAction _redoAction = new RedoAction();
154
155    /** A compound undo edit, or null if none is progress. */
156    protected CompoundEdit _compoundEdit;
157
158    /** The undo action. */
159    protected UndoAction _undoAction = new UndoAction();
160
161    /** The undo manager. */
162    protected UndoManager _undo = new UndoManager();
163
164    /**
165     * Perform the undo action.
166     */
167    @SuppressWarnings("serial")
168    protected class UndoAction extends AbstractAction {
169        public UndoAction() {
170            super("Undo");
171            setEnabled(false);
172        }
173
174        @Override
175        public void actionPerformed(ActionEvent e) {
176            synchronized (UndoListener.this) {
177                try {
178                    _undo.undo();
179                } catch (CannotUndoException ex) {
180                    throw new RuntimeException("Unable to undo.", ex);
181                }
182                _updateUndoState();
183                _redoAction._updateRedoState();
184            }
185        }
186
187        /** Depending on the whether the undo manager can undo, enable
188         *  and disable the undo state.
189         */
190        protected void _updateUndoState() {
191            if (_undo.canUndo()) {
192                setEnabled(true);
193            } else {
194                setEnabled(false);
195            }
196        }
197    }
198
199    /**
200     * Perform the redo action.
201     */
202    @SuppressWarnings("serial")
203    protected class RedoAction extends AbstractAction {
204        public RedoAction() {
205            super("Redo");
206            setEnabled(false);
207        }
208
209        @Override
210        public void actionPerformed(ActionEvent e) {
211            synchronized (UndoListener.this) {
212                try {
213                    _undo.redo();
214                } catch (CannotRedoException ex) {
215                    throw new RuntimeException("Unable to redo.", ex);
216                }
217                _updateRedoState();
218                _undoAction._updateUndoState();
219            }
220        }
221
222        /** Depending on the whether the undo manager can redo, enable
223         *  and disable the undo state.
224         */
225        protected void _updateRedoState() {
226            if (_undo.canRedo()) {
227                setEnabled(true);
228            } else {
229                setEnabled(false);
230            }
231        }
232    }
233}