1. /*
  2. * @(#)UndoManager.java 1.28 01/04/21
  3. *
  4. * Copyright 1997-2001 Sun Microsystems, Inc. All Rights Reserved.
  5. *
  6. * This software is the proprietary information of Sun Microsystems, Inc.
  7. * Use is subject to license terms.
  8. *
  9. */
  10. package javax.swing.undo;
  11. import javax.swing.event.*;
  12. import javax.swing.UIManager;
  13. import java.util.*;
  14. /**
  15. * Concrete subclass of CompoundEdit which can serve as an
  16. * UndoableEditListener, consolidating the UndoableEditEvents from a
  17. * variety of sources, and undoing or redoing them one at a time.
  18. *
  19. * Unlike AbstractUndoableEdit and CompoundEdit, the public methods of this
  20. * class are synchronized, and should be safe to call from multiple
  21. * threads. This should make UndoManager a convenient marshall for
  22. * sets of undoable JavaBeans.
  23. * <p>
  24. * <strong>Warning:</strong>
  25. * Serialized objects of this class will not be compatible with
  26. * future Swing releases. The current serialization support is appropriate
  27. * for short term storage or RMI between applications running the same
  28. * version of Swing. A future release of Swing will provide support for
  29. * long term persistence.
  30. *
  31. * @author Ray Ryan
  32. * @version 1.28, 04/21/01
  33. */
  34. public class UndoManager extends CompoundEdit implements UndoableEditListener {
  35. int indexOfNextAdd;
  36. int limit;
  37. public UndoManager() {
  38. super();
  39. indexOfNextAdd = 0;
  40. limit = 100;
  41. edits.ensureCapacity(limit);
  42. }
  43. /**
  44. * Returns the maximum number of edits this UndoManager will
  45. * hold. Default value is 100.
  46. *
  47. * @see #addEdit
  48. * @see #setLimit
  49. */
  50. public synchronized int getLimit() {
  51. return limit;
  52. }
  53. /**
  54. * Empty the undo manager, sending each edit a die message
  55. * in the process.
  56. */
  57. public synchronized void discardAllEdits() {
  58. Enumeration cursor = edits.elements();
  59. while (cursor.hasMoreElements()) {
  60. UndoableEdit e = (UndoableEdit)cursor.nextElement();
  61. e.die();
  62. }
  63. edits = new Vector(limit);
  64. indexOfNextAdd = 0;
  65. // PENDING(rjrjr) when vector grows a removeRange() method
  66. // (expected in JDK 1.2), trimEdits() will be nice and
  67. // efficient, and this method can call that instead.
  68. }
  69. /**
  70. * Reduce the number of queued edits to a range of size limit,
  71. * centered on indexOfNextAdd.
  72. */
  73. protected void trimForLimit() {
  74. if (limit > 0) {
  75. int size = edits.size();
  76. // System.out.print("limit: " + limit +
  77. // " size: " + size +
  78. // " indexOfNextAdd: " + indexOfNextAdd +
  79. // "\n");
  80. if (size > limit) {
  81. int halfLimit = limit2;
  82. int keepFrom = indexOfNextAdd - 1 - halfLimit;
  83. int keepTo = indexOfNextAdd - 1 + halfLimit;
  84. // These are ints we're playing with, so dividing by two
  85. // rounds down for odd numbers, so make sure the limit was
  86. // honored properly. Note that the keep range is
  87. // inclusive.
  88. if (keepTo - keepFrom + 1 > limit) {
  89. keepFrom++;
  90. }
  91. // The keep range is centered on indexOfNextAdd,
  92. // but odds are good that the actual edits Vector
  93. // isn't. Move the keep range to keep it legal.
  94. if (keepFrom < 0) {
  95. keepTo -= keepFrom;
  96. keepFrom = 0;
  97. }
  98. if (keepTo >= size) {
  99. int delta = size - keepTo - 1;
  100. keepTo += delta;
  101. keepFrom += delta;
  102. }
  103. // System.out.println("Keeping " + keepFrom + " " + keepTo);
  104. trimEdits(keepTo+1, size-1);
  105. trimEdits(0, keepFrom-1);
  106. }
  107. }
  108. }
  109. /**
  110. * Tell the edits in the given range (inclusive) to die, and
  111. * remove them from edits. from > to is a no-op.
  112. */
  113. protected void trimEdits(int from, int to) {
  114. if (from <= to) {
  115. // System.out.println("Trimming " + from + " " + to + " with index " +
  116. // indexOfNextAdd);
  117. for (int i = to; from <= i; i--) {
  118. UndoableEdit e = (UndoableEdit)edits.elementAt(i);
  119. // System.out.println("JUM: Discarding " +
  120. // e.getUndoPresentationName());
  121. e.die();
  122. // PENDING(rjrjr) when Vector supports range deletion (JDK
  123. // 1.2) , we can optimize the next line considerably.
  124. edits.removeElementAt(i);
  125. }
  126. if (indexOfNextAdd > to) {
  127. // System.out.print("...right...");
  128. indexOfNextAdd -= to-from+1;
  129. } else if (indexOfNextAdd >= from) {
  130. // System.out.println("...mid...");
  131. indexOfNextAdd = from;
  132. }
  133. // System.out.println("new index " + indexOfNextAdd);
  134. }
  135. }
  136. /**
  137. * Set the maximum number of edits this UndoManager will hold. If
  138. * edits need to be discarded to shrink the limit, they will be
  139. * told to die in the reverse of the order that they were added.
  140. *
  141. * @see #addEdit
  142. * @see #getLimit
  143. */
  144. public synchronized void setLimit(int l) {
  145. if (!inProgress) throw new RuntimeException("Attempt to call UndoManager.setLimit() after UndoManager.end() has been called");
  146. limit = l;
  147. trimForLimit();
  148. }
  149. /**
  150. * Returns the the next significant edit to be undone if undo is
  151. * called. May return null
  152. */
  153. protected UndoableEdit editToBeUndone() {
  154. int i = indexOfNextAdd;
  155. while (i > 0) {
  156. UndoableEdit edit = (UndoableEdit)edits.elementAt(--i);
  157. if (edit.isSignificant()) {
  158. return edit;
  159. }
  160. }
  161. return null;
  162. }
  163. /**
  164. * Returns the the next significant edit to be redone if redo is
  165. * called. May return null
  166. */
  167. protected UndoableEdit editToBeRedone() {
  168. int count = edits.size();
  169. int i = indexOfNextAdd;
  170. while (i < count) {
  171. UndoableEdit edit = (UndoableEdit)edits.elementAt(i++);
  172. if (edit.isSignificant()) {
  173. return edit;
  174. }
  175. }
  176. return null;
  177. }
  178. /**
  179. * Undoes all changes from indexOfNextAdd to edit. Updates indexOfNextAdd accordingly.
  180. */
  181. protected void undoTo(UndoableEdit edit) throws CannotUndoException {
  182. boolean done = false;
  183. while (!done) {
  184. UndoableEdit next = (UndoableEdit)edits.elementAt(--indexOfNextAdd);
  185. next.undo();
  186. done = next == edit;
  187. }
  188. }
  189. /**
  190. * Redoes all changes from indexOfNextAdd to edit. Updates indexOfNextAdd accordingly.
  191. */
  192. protected void redoTo(UndoableEdit edit) throws CannotRedoException {
  193. boolean done = false;
  194. while (!done) {
  195. UndoableEdit next = (UndoableEdit)edits.elementAt(indexOfNextAdd++);
  196. next.redo();
  197. done = next == edit;
  198. }
  199. }
  200. /**
  201. * Undo or redo as appropriate. Suitable for binding to an action
  202. * that toggles between these two functions. Only makes sense
  203. * to send this if limit == 1.
  204. *
  205. * @see #canUndoOrRedo
  206. * @see #getUndoOrRedoPresentationName
  207. */
  208. public synchronized void undoOrRedo() throws CannotRedoException,
  209. CannotUndoException {
  210. if (indexOfNextAdd == edits.size()) {
  211. undo();
  212. } else {
  213. redo();
  214. }
  215. }
  216. /**
  217. * Return true if calling undoOrRedo will undo or redo. Suitable
  218. * for deciding to enable a command that toggles between the two
  219. * functions, which only makes sense to use if limit == 1.
  220. *
  221. * @see #undoOrRedo
  222. */
  223. public synchronized boolean canUndoOrRedo() {
  224. if (indexOfNextAdd == edits.size()) {
  225. return canUndo();
  226. } else {
  227. return canRedo();
  228. }
  229. }
  230. /**
  231. * If this UndoManager is inProgress, undo the last significant
  232. * UndoableEdit before indexOfNextAdd, and all insignificant edits back to
  233. * it. Updates indexOfNextAdd accordingly.
  234. *
  235. * <p>If not inProgress, indexOfNextAdd is ignored and super's routine is
  236. * called.</p>
  237. *
  238. * @see CompoundEdit#end
  239. */
  240. public synchronized void undo() throws CannotUndoException {
  241. if (inProgress) {
  242. UndoableEdit edit = editToBeUndone();
  243. if (edit == null) {
  244. throw new CannotUndoException();
  245. }
  246. undoTo(edit);
  247. } else {
  248. super.undo();
  249. }
  250. }
  251. /**
  252. * Overridden to preserve usual semantics: returns true if an undo
  253. * operation would be successful now, false otherwise
  254. */
  255. public synchronized boolean canUndo() {
  256. if (inProgress) {
  257. UndoableEdit edit = editToBeUndone();
  258. return edit != null && edit.canUndo();
  259. } else {
  260. return super.canUndo();
  261. }
  262. }
  263. /**
  264. * If this UndoManager is inProgress, redoes the last significant
  265. * UndoableEdit at indexOfNextAdd or after, and all insignificant
  266. * edits up to it. Updates indexOfNextAdd accordingly.
  267. *
  268. * <p>If not inProgress, indexOfNextAdd is ignored and super's routine is
  269. * called.</p>
  270. *
  271. * @see CompoundEdit#end
  272. */
  273. public synchronized void redo() throws CannotRedoException {
  274. if (inProgress) {
  275. UndoableEdit edit = editToBeRedone();
  276. if (edit == null) {
  277. throw new CannotRedoException();
  278. }
  279. redoTo(edit);
  280. } else {
  281. super.redo();
  282. }
  283. }
  284. /**
  285. * Overridden to preserve usual semantics: returns true if a redo
  286. * operation would be successful now, false otherwise
  287. */
  288. public synchronized boolean canRedo() {
  289. if (inProgress) {
  290. UndoableEdit edit = editToBeRedone();
  291. return edit != null && edit.canRedo();
  292. } else {
  293. return super.canRedo();
  294. }
  295. }
  296. /**
  297. * If inProgress, inserts anEdit at indexOfNextAdd, and removes
  298. * any old edits that were at indexOfNextAdd or later. The die
  299. * method is called on each edit that is removed is sent, in the
  300. * reverse of the order the edits were added. Updates
  301. * indexOfNextAdd.
  302. *
  303. * <p>If not inProgress, acts as a CompoundEdit</p>
  304. *
  305. * @see CompoundEdit#end
  306. * @see CompoundEdit#addEdit
  307. */
  308. public synchronized boolean addEdit(UndoableEdit anEdit) {
  309. boolean retVal;
  310. // Trim from the indexOfNextAdd to the end, as we'll
  311. // never reach these edits once the new one is added.
  312. trimEdits(indexOfNextAdd, edits.size()-1);
  313. retVal = super.addEdit(anEdit);
  314. if (inProgress) {
  315. retVal = true;
  316. }
  317. // Maybe super added this edit, maybe it didn't (perhaps
  318. // an in progress compound edit took it instead. Or perhaps
  319. // this UndoManager is no longer in progress). So make sure
  320. // the indexOfNextAdd is pointed at the right place.
  321. indexOfNextAdd = edits.size();
  322. // Enforce the limit
  323. trimForLimit();
  324. return retVal;
  325. }
  326. /**
  327. * Sending end() to an UndoManager turns it into a plain old
  328. * (ended) CompoundEdit.
  329. *
  330. * <p> Calls super's end() method (making inProgress false), then
  331. * sends die() to the unreachable edits at indexOfNextAdd and
  332. * beyond, in the reverse of the order in which they were added.
  333. *
  334. * @see CompoundEdit#end
  335. */
  336. public synchronized void end() {
  337. super.end();
  338. this.trimEdits(indexOfNextAdd, edits.size()-1);
  339. }
  340. /**
  341. * Return the appropriate name for a command that toggles between
  342. * undo and redo. Only makes sense to use such a command if limit
  343. * == 1 and we're not in progress.
  344. */
  345. public synchronized String getUndoOrRedoPresentationName() {
  346. if (indexOfNextAdd == edits.size()) {
  347. return getUndoPresentationName();
  348. } else {
  349. return getRedoPresentationName();
  350. }
  351. }
  352. /**
  353. * If inProgress, returns getUndoPresentationName of the
  354. * significant edit that will be undone when undo() is invoked.
  355. * If there is none, returns AbstractUndoableEdit.undoText from the
  356. * defaults table.
  357. *
  358. * <p>If not inProgress, acts as a CompoundEdit</p>
  359. *
  360. * @see #undo
  361. * @see CompoundEdit#getUndoPresentationName
  362. */
  363. public synchronized String getUndoPresentationName() {
  364. if (inProgress) {
  365. if (canUndo()) {
  366. return editToBeUndone().getUndoPresentationName();
  367. } else {
  368. return UIManager.getString("AbstractUndoableEdit.undoText");
  369. }
  370. } else {
  371. return super.getUndoPresentationName();
  372. }
  373. }
  374. /**
  375. * If inProgress, returns getRedoPresentationName of the
  376. * significant edit that will be redone when redo() is invoked.
  377. * If there is none, returns AbstractUndoableEdit.redoText from the
  378. * defaults table.
  379. *
  380. * <p>If not inProgress, acts as a CompoundEdit</p>
  381. *
  382. * @see #redo
  383. * @see CompoundEdit#getUndoPresentationName
  384. */
  385. public synchronized String getRedoPresentationName() {
  386. if (inProgress) {
  387. if (canRedo()) {
  388. return editToBeRedone().getRedoPresentationName();
  389. } else {
  390. return UIManager.getString("AbstractUndoableEdit.redoText");
  391. }
  392. } else {
  393. return super.getRedoPresentationName();
  394. }
  395. }
  396. /**
  397. * Called by the UndoabledEdit sources this UndoManager listens
  398. * to. Calls addEdit with e.getEdit().
  399. *
  400. * @see #addEdit
  401. */
  402. public void undoableEditHappened(UndoableEditEvent e) {
  403. addEdit(e.getEdit());
  404. }
  405. /**
  406. * Returns a string that displays and identifies this
  407. * object's properties.
  408. *
  409. * @return a String representation of this object
  410. */
  411. public String toString() {
  412. return super.toString() + " limit: " + limit +
  413. " indexOfNextAdd: " + indexOfNextAdd;
  414. }
  415. }