001: /*BEGIN_COPYRIGHT_BLOCK
002: *
003: * Copyright (c) 2001-2007, JavaPLT group at Rice University (javaplt@rice.edu)
004: * All rights reserved.
005: *
006: * Redistribution and use in source and binary forms, with or without
007: * modification, are permitted provided that the following conditions are met:
008: * * Redistributions of source code must retain the above copyright
009: * notice, this list of conditions and the following disclaimer.
010: * * Redistributions in binary form must reproduce the above copyright
011: * notice, this list of conditions and the following disclaimer in the
012: * documentation and/or other materials provided with the distribution.
013: * * Neither the names of DrJava, the JavaPLT group, Rice University, nor the
014: * names of its contributors may be used to endorse or promote products
015: * derived from this software without specific prior written permission.
016: *
017: * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
018: * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
019: * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
020: * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
021: * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
022: * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
023: * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
024: * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
025: * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
026: * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
027: * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
028: *
029: * This software is Open Source Initiative approved Open Source Software.
030: * Open Source Initative Approved is a trademark of the Open Source Initiative.
031: *
032: * This file is part of DrJava. Download the current version of this project
033: * from http://www.drjava.org/ or http://sourceforge.net/projects/drjava/
034: *
035: * END_COPYRIGHT_BLOCK*/
036:
037: package edu.rice.cs.drjava.model.definitions;
038:
039: import javax.swing.undo.*;
040: import javax.swing.SwingUtilities;
041: import java.util.LinkedList;
042:
043: import edu.rice.cs.drjava.model.GlobalEventNotifier;
044: import edu.rice.cs.util.swing.Utilities;
045:
046: /** Extended UndoManager with increased functionality. Can handle aggregating multiple edits into one for the purposes
047: * of undoing and redoing. It exposes editToBeUndone and editToBeRedone (under new names); they are protected methods
048: * in UndoManager. The public methods that involve composite state are synchronized, so this manager can be accessed
049: * outside of the event thread. The internal data structures _compoundEdits and _keys are not thread safe but they
050: * only accessed only by synchronized methods. The synchronization scheme (locking on this) follows UndoManager.
051: * @version $Id: CompoundUndoManager.java 4255 2007-08-28 19:17:37Z mgricken $
052: */
053: public class CompoundUndoManager extends UndoManager {
054:
055: private static volatile int counter = 0;
056:
057: private final int id;
058:
059: /** The compound edits we are storing. Not thread safe! */
060: private final LinkedList<CompoundEdit> _compoundEdits;
061:
062: /** The keys for the CompoundEdits we are storing. */
063: private final LinkedList<Integer> _keys;
064:
065: /** The next key to use for nested CompoundEdits. */
066: private volatile int _nextKey;
067:
068: /** The last edit that was performed before the last save. */
069: private volatile UndoableEdit _savePoint;
070:
071: /** Keeps track of the listeners to this undo manager. */
072: private final GlobalEventNotifier _notifier;
073:
074: /** Standard constructor. */
075: public CompoundUndoManager(GlobalEventNotifier notifier) {
076: super ();
077: counter++;
078: id = counter;
079: _compoundEdits = new LinkedList<CompoundEdit>();
080: _keys = new LinkedList<Integer>();
081: _nextKey = 0;
082: _savePoint = null;
083: _notifier = notifier;
084: }
085:
086: /** Starts a compound edit.
087: * @return the key for the compound edit
088: */
089: public synchronized int startCompoundEdit() {
090: _compoundEdits.add(0, new CompoundEdit());
091: _keys.add(0, new Integer(_nextKey));
092: if (_nextKey < Integer.MAX_VALUE)
093: _nextKey++;
094: else
095: _nextKey = Integer.MIN_VALUE;
096: return _keys.get(0).intValue();
097: }
098:
099: /** Ends the last compound edit that was created.
100: * Used when a compound edit is created by the _undoListener in DefinitionsPane and the key is not known in DefinitionsDocument.
101: */
102: public synchronized void endLastCompoundEdit() {
103: if (_keys.size() == 0)
104: return;
105: // NOTE: The preceding can happen if for example uncomment lines does not modify any text.
106: endCompoundEdit(_keys.get(0).intValue());
107: }
108:
109: /** Ends a compound edit.
110: * @param key the key that was returned by startCompoundEdit()
111: */
112: public synchronized void endCompoundEdit(int key) {
113: if (_keys.size() == 0)
114: return;
115:
116: if (_keys.get(0) == key) {
117: _keys.remove(0);
118: final CompoundEdit ce = _compoundEdits.remove(0);
119:
120: ce.end();
121: if (ce.canUndo()) {
122: if (!_compoundEditInProgress()) {
123: super .addEdit(ce);
124: _notifyUndoHappened();
125: } else
126: _compoundEdits.get(0).addEdit(ce);
127: }
128: } else
129: throw new IllegalStateException(
130: "Improperly nested compound edits.");
131: }
132:
133: /** We are getting the last Compound Edit entered into the list.
134: * This is for making a Compound edit for granular undo.
135: */
136: public synchronized CompoundEdit getLastCompoundEdit() {
137: return _compoundEdits.get(0);
138: }
139:
140: /** Gets the next undo.
141: * @return the next undo
142: */
143: public UndoableEdit getNextUndo() {
144: return editToBeUndone();
145: }
146:
147: /** Gets the next redo.
148: * @return the next redo
149: */
150: public UndoableEdit getNextRedo() {
151: return editToBeRedone();
152: }
153:
154: /** Adds an edit. Checks whether or not the current edit is a compound edit.
155: * @param e the edit to be added
156: * @return true if the add is successful, false otherwise
157: */
158: public synchronized boolean addEdit(UndoableEdit e) {
159: if (_compoundEditInProgress()) {
160: // _notifyUndoHappened(); // added this for granular undo
161: return _compoundEdits.get(0).addEdit(e);
162: } else {
163: boolean result = super .addEdit(e);
164: _notifyUndoHappened();
165: return result;
166: }
167: }
168:
169: /** Returns whether or not a compound edit is in progress.
170: * @return true iff in progress
171: */
172: public synchronized boolean _compoundEditInProgress() {
173: return !_compoundEdits.isEmpty();
174: }
175:
176: /** Returns true when a compound edit is in progress, or when there are valid stored undoable edits
177: * @return true iff undoing is possible
178: */
179: public synchronized boolean canUndo() {
180: return _compoundEditInProgress() || super .canUndo();
181: }
182:
183: /** Returns the presentation name for this undo, or delegates to super if none is available
184: * @return the undo's presentation name
185: */
186: public synchronized String getUndoPresentationName() {
187: if (_compoundEditInProgress())
188: return "Undo Previous Command";
189: return super .getUndoPresentationName();
190: }
191:
192: /** Undoes the last undoable edit, or compound edit created by the user. */
193: public synchronized void undo() {
194: endCompoundEdit();
195: super .undo();
196: }
197:
198: /** Overload for undo which allows the initiator of a CompoundEdit to abandon it.
199: * WARNING: this has been used to date and has not been properly tested and very possibly may not work.
200: * @param key the key returned by the last call to startCompoundEdit
201: * @throws IllegalArgumentException if the key is incorrect
202: */
203: public synchronized void undo(int key) {
204: if (_keys.get(0) == key) {
205: final CompoundEdit ce = _compoundEdits.get(0);
206: _compoundEdits.remove(0);
207: _keys.remove(0);
208:
209: SwingUtilities.invokeLater(new Runnable() {
210: public void run() {
211: ce.end();
212: ce.undo();
213: ce.die();
214: }
215: }); // unsafe methods inherited from CompoundEdit
216: } else
217: throw new IllegalArgumentException("Bad undo key " + key
218: + "!");
219: }
220:
221: /** Overrides redo so that any compound edit in progress is ended before the redo is performed. */
222: public synchronized void redo() {
223: endCompoundEdit(); // How can there be a compound edit in progress if redo is available?
224: super .redo();
225: }
226:
227: /** Helper method to notify the view that an undoable edit has occured. Note that lock on this is not held by
228: * the event thread (even if called from event thread) when notification happens. */
229: private void _notifyUndoHappened() {
230: // Use SwingUtilities.invokeLater so that notification is deferred when running in the event thread.
231: SwingUtilities.invokeLater(new Runnable() {
232: public void run() {
233: _notifier.undoableEditHappened();
234: }
235: });
236: }
237:
238: /** Ends the compoundEdit in progress if any. Used by undo(), redo(), documentSaved(). */
239: private synchronized void endCompoundEdit() {
240: Integer[] keys = _keys.toArray(new Integer[_keys.size()]); // unit testing ran into a concurrent modification exception without this copying operation
241: if (_compoundEditInProgress()) {
242: for (int key : keys)
243: endCompoundEdit(key);
244: }
245: }
246:
247: /** Informs this undo manager that the document has been saved. */
248: public synchronized void documentSaved() {
249: endCompoundEdit();
250: _savePoint = editToBeUndone();
251: // Utilities.showDebug("_savePoint := " + _savePoint);
252: }
253:
254: /** Determines if the document is in the same undo state as it was when it was last saved.
255: * @return true iff all changes have been undone since the last save
256: */
257: public synchronized boolean isModified() {
258: // Utilities.showDebug("_savePoint = " + _savePoint + " editToBeUndone() = " + editToBeUndone());
259: return editToBeUndone() != _savePoint;
260: }
261:
262: public String toString() {
263: return "(CompoundUndoManager: " + id + ")";
264: }
265:
266: /** Used to help track down memory leaks. */
267: // protected void finalize() throws Throwable{
268: // super.finalize();
269: // }
270: }
|