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}