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