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