mxUndoManager.js 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229
  1. /**
  2. * Copyright (c) 2006-2015, JGraph Ltd
  3. * Copyright (c) 2006-2015, Gaudenz Alder
  4. */
  5. /**
  6. * Class: mxUndoManager
  7. *
  8. * Implements a command history. When changing the graph model, an
  9. * <mxUndoableChange> object is created at the start of the transaction (when
  10. * model.beginUpdate is called). All atomic changes are then added to this
  11. * object until the last model.endUpdate call, at which point the
  12. * <mxUndoableEdit> is dispatched in an event, and added to the history inside
  13. * <mxUndoManager>. This is done by an event listener in
  14. * <mxEditor.installUndoHandler>.
  15. *
  16. * Each atomic change of the model is represented by an object (eg.
  17. * <mxRootChange>, <mxChildChange>, <mxTerminalChange> etc) which contains the
  18. * complete undo information. The <mxUndoManager> also listens to the
  19. * <mxGraphView> and stores it's changes to the current root as insignificant
  20. * undoable changes, so that drilling (step into, step up) is undone.
  21. *
  22. * This means when you execute an atomic change on the model, then change the
  23. * current root on the view and click undo, the change of the root will be
  24. * undone together with the change of the model so that the display represents
  25. * the state at which the model was changed. However, these changes are not
  26. * transmitted for sharing as they do not represent a state change.
  27. *
  28. * Example:
  29. *
  30. * When adding an undo manager to a graph, make sure to add it
  31. * to the model and the view as well to maintain a consistent
  32. * display across multiple undo/redo steps.
  33. *
  34. * (code)
  35. * var undoManager = new mxUndoManager();
  36. * var listener = function(sender, evt)
  37. * {
  38. * undoManager.undoableEditHappened(evt.getProperty('edit'));
  39. * };
  40. * graph.getModel().addListener(mxEvent.UNDO, listener);
  41. * graph.getView().addListener(mxEvent.UNDO, listener);
  42. * (end)
  43. *
  44. * The code creates a function that informs the undoManager
  45. * of an undoable edit and binds it to the undo event of
  46. * <mxGraphModel> and <mxGraphView> using
  47. * <mxEventSource.addListener>.
  48. *
  49. * Event: mxEvent.CLEAR
  50. *
  51. * Fires after <clear> was invoked. This event has no properties.
  52. *
  53. * Event: mxEvent.UNDO
  54. *
  55. * Fires afer a significant edit was undone in <undo>. The <code>edit</code>
  56. * property contains the <mxUndoableEdit> that was undone.
  57. *
  58. * Event: mxEvent.REDO
  59. *
  60. * Fires afer a significant edit was redone in <redo>. The <code>edit</code>
  61. * property contains the <mxUndoableEdit> that was redone.
  62. *
  63. * Event: mxEvent.ADD
  64. *
  65. * Fires after an undoable edit was added to the history. The <code>edit</code>
  66. * property contains the <mxUndoableEdit> that was added.
  67. *
  68. * Constructor: mxUndoManager
  69. *
  70. * Constructs a new undo manager with the given history size. If no history
  71. * size is given, then a default size of 100 steps is used.
  72. */
  73. function mxUndoManager(size)
  74. {
  75. this.size = (size != null) ? size : 100;
  76. this.clear();
  77. };
  78. /**
  79. * Extends mxEventSource.
  80. */
  81. mxUndoManager.prototype = new mxEventSource();
  82. mxUndoManager.prototype.constructor = mxUndoManager;
  83. /**
  84. * Variable: size
  85. *
  86. * Maximum command history size. 0 means unlimited history. Default is
  87. * 100.
  88. */
  89. mxUndoManager.prototype.size = null;
  90. /**
  91. * Variable: history
  92. *
  93. * Array that contains the steps of the command history.
  94. */
  95. mxUndoManager.prototype.history = null;
  96. /**
  97. * Variable: indexOfNextAdd
  98. *
  99. * Index of the element to be added next.
  100. */
  101. mxUndoManager.prototype.indexOfNextAdd = 0;
  102. /**
  103. * Function: isEmpty
  104. *
  105. * Returns true if the history is empty.
  106. */
  107. mxUndoManager.prototype.isEmpty = function()
  108. {
  109. return this.history.length == 0;
  110. };
  111. /**
  112. * Function: clear
  113. *
  114. * Clears the command history.
  115. */
  116. mxUndoManager.prototype.clear = function()
  117. {
  118. this.history = [];
  119. this.indexOfNextAdd = 0;
  120. this.fireEvent(new mxEventObject(mxEvent.CLEAR));
  121. };
  122. /**
  123. * Function: canUndo
  124. *
  125. * Returns true if an undo is possible.
  126. */
  127. mxUndoManager.prototype.canUndo = function()
  128. {
  129. return this.indexOfNextAdd > 0;
  130. };
  131. /**
  132. * Function: undo
  133. *
  134. * Undoes the last change.
  135. */
  136. mxUndoManager.prototype.undo = function()
  137. {
  138. while (this.indexOfNextAdd > 0)
  139. {
  140. var edit = this.history[--this.indexOfNextAdd];
  141. edit.undo();
  142. if (edit.isSignificant())
  143. {
  144. this.fireEvent(new mxEventObject(mxEvent.UNDO, 'edit', edit));
  145. break;
  146. }
  147. }
  148. };
  149. /**
  150. * Function: canRedo
  151. *
  152. * Returns true if a redo is possible.
  153. */
  154. mxUndoManager.prototype.canRedo = function()
  155. {
  156. return this.indexOfNextAdd < this.history.length;
  157. };
  158. /**
  159. * Function: redo
  160. *
  161. * Redoes the last change.
  162. */
  163. mxUndoManager.prototype.redo = function()
  164. {
  165. var n = this.history.length;
  166. while (this.indexOfNextAdd < n)
  167. {
  168. var edit = this.history[this.indexOfNextAdd++];
  169. edit.redo();
  170. if (edit.isSignificant())
  171. {
  172. this.fireEvent(new mxEventObject(mxEvent.REDO, 'edit', edit));
  173. break;
  174. }
  175. }
  176. };
  177. /**
  178. * Function: undoableEditHappened
  179. *
  180. * Method to be called to add new undoable edits to the <history>.
  181. */
  182. mxUndoManager.prototype.undoableEditHappened = function(undoableEdit)
  183. {
  184. this.trim();
  185. if (this.size > 0 &&
  186. this.size == this.history.length)
  187. {
  188. this.history.shift();
  189. }
  190. this.history.push(undoableEdit);
  191. this.indexOfNextAdd = this.history.length;
  192. this.fireEvent(new mxEventObject(mxEvent.ADD, 'edit', undoableEdit));
  193. };
  194. /**
  195. * Function: trim
  196. *
  197. * Removes all pending steps after <indexOfNextAdd> from the history,
  198. * invoking die on each edit. This is called from <undoableEditHappened>.
  199. */
  200. mxUndoManager.prototype.trim = function()
  201. {
  202. if (this.history.length > this.indexOfNextAdd)
  203. {
  204. var edits = this.history.splice(this.indexOfNextAdd,
  205. this.history.length - this.indexOfNextAdd);
  206. for (var i = 0; i < edits.length; i++)
  207. {
  208. edits[i].die();
  209. }
  210. }
  211. };