diff options
Diffstat (limited to 'src/js/util/mxUndoManager.js')
-rw-r--r-- | src/js/util/mxUndoManager.js | 229 |
1 files changed, 229 insertions, 0 deletions
diff --git a/src/js/util/mxUndoManager.js b/src/js/util/mxUndoManager.js new file mode 100644 index 0000000..2cb93cb --- /dev/null +++ b/src/js/util/mxUndoManager.js @@ -0,0 +1,229 @@ +/** + * $Id: mxUndoManager.js,v 1.30 2011-10-05 06:39:19 gaudenz Exp $ + * Copyright (c) 2006-2010, JGraph Ltd + */ +/** + * Class: mxUndoManager + * + * Implements a command history. When changing the graph model, an + * <mxUndoableChange> object is created at the start of the transaction (when + * model.beginUpdate is called). All atomic changes are then added to this + * object until the last model.endUpdate call, at which point the + * <mxUndoableEdit> is dispatched in an event, and added to the history inside + * <mxUndoManager>. This is done by an event listener in + * <mxEditor.installUndoHandler>. + * + * Each atomic change of the model is represented by an object (eg. + * <mxRootChange>, <mxChildChange>, <mxTerminalChange> etc) which contains the + * complete undo information. The <mxUndoManager> also listens to the + * <mxGraphView> and stores it's changes to the current root as insignificant + * undoable changes, so that drilling (step into, step up) is undone. + * + * This means when you execute an atomic change on the model, then change the + * current root on the view and click undo, the change of the root will be + * undone together with the change of the model so that the display represents + * the state at which the model was changed. However, these changes are not + * transmitted for sharing as they do not represent a state change. + * + * Example: + * + * When adding an undo manager to a graph, make sure to add it + * to the model and the view as well to maintain a consistent + * display across multiple undo/redo steps. + * + * (code) + * var undoManager = new mxUndoManager(); + * var listener = function(sender, evt) + * { + * undoManager.undoableEditHappened(evt.getProperty('edit')); + * }; + * graph.getModel().addListener(mxEvent.UNDO, listener); + * graph.getView().addListener(mxEvent.UNDO, listener); + * (end) + * + * The code creates a function that informs the undoManager + * of an undoable edit and binds it to the undo event of + * <mxGraphModel> and <mxGraphView> using + * <mxEventSource.addListener>. + * + * Event: mxEvent.CLEAR + * + * Fires after <clear> was invoked. This event has no properties. + * + * Event: mxEvent.UNDO + * + * Fires afer a significant edit was undone in <undo>. The <code>edit</code> + * property contains the <mxUndoableEdit> that was undone. + * + * Event: mxEvent.REDO + * + * Fires afer a significant edit was redone in <redo>. The <code>edit</code> + * property contains the <mxUndoableEdit> that was redone. + * + * Event: mxEvent.ADD + * + * Fires after an undoable edit was added to the history. The <code>edit</code> + * property contains the <mxUndoableEdit> that was added. + * + * Constructor: mxUndoManager + * + * Constructs a new undo manager with the given history size. If no history + * size is given, then a default size of 100 steps is used. + */ +function mxUndoManager(size) +{ + this.size = (size != null) ? size : 100; + this.clear(); +}; + +/** + * Extends mxEventSource. + */ +mxUndoManager.prototype = new mxEventSource(); +mxUndoManager.prototype.constructor = mxUndoManager; + +/** + * Variable: size + * + * Maximum command history size. 0 means unlimited history. Default is + * 100. + */ +mxUndoManager.prototype.size = null; + +/** + * Variable: history + * + * Array that contains the steps of the command history. + */ +mxUndoManager.prototype.history = null; + +/** + * Variable: indexOfNextAdd + * + * Index of the element to be added next. + */ +mxUndoManager.prototype.indexOfNextAdd = 0; + +/** + * Function: isEmpty + * + * Returns true if the history is empty. + */ +mxUndoManager.prototype.isEmpty = function() +{ + return this.history.length == 0; +}; + +/** + * Function: clear + * + * Clears the command history. + */ +mxUndoManager.prototype.clear = function() +{ + this.history = []; + this.indexOfNextAdd = 0; + this.fireEvent(new mxEventObject(mxEvent.CLEAR)); +}; + +/** + * Function: canUndo + * + * Returns true if an undo is possible. + */ +mxUndoManager.prototype.canUndo = function() +{ + return this.indexOfNextAdd > 0; +}; + +/** + * Function: undo + * + * Undoes the last change. + */ +mxUndoManager.prototype.undo = function() +{ + while (this.indexOfNextAdd > 0) + { + var edit = this.history[--this.indexOfNextAdd]; + edit.undo(); + + if (edit.isSignificant()) + { + this.fireEvent(new mxEventObject(mxEvent.UNDO, 'edit', edit)); + break; + } + } +}; + +/** + * Function: canRedo + * + * Returns true if a redo is possible. + */ +mxUndoManager.prototype.canRedo = function() +{ + return this.indexOfNextAdd < this.history.length; +}; + +/** + * Function: redo + * + * Redoes the last change. + */ +mxUndoManager.prototype.redo = function() +{ + var n = this.history.length; + + while (this.indexOfNextAdd < n) + { + var edit = this.history[this.indexOfNextAdd++]; + edit.redo(); + + if (edit.isSignificant()) + { + this.fireEvent(new mxEventObject(mxEvent.REDO, 'edit', edit)); + break; + } + } +}; + +/** + * Function: undoableEditHappened + * + * Method to be called to add new undoable edits to the <history>. + */ +mxUndoManager.prototype.undoableEditHappened = function(undoableEdit) +{ + this.trim(); + + if (this.size > 0 && + this.size == this.history.length) + { + this.history.shift(); + } + + this.history.push(undoableEdit); + this.indexOfNextAdd = this.history.length; + this.fireEvent(new mxEventObject(mxEvent.ADD, 'edit', undoableEdit)); +}; + +/** + * Function: trim + * + * Removes all pending steps after <indexOfNextAdd> from the history, + * invoking die on each edit. This is called from <undoableEditHappened>. + */ +mxUndoManager.prototype.trim = function() +{ + if (this.history.length > this.indexOfNextAdd) + { + var edits = this.history.splice(this.indexOfNextAdd, + this.history.length - this.indexOfNextAdd); + + for (var i = 0; i < edits.length; i++) + { + edits[i].die(); + } + } +}; |