/** * Copyright (c) 2006-2015, JGraph Ltd * Copyright (c) 2006-2015, Gaudenz Alder */ /** * Class: mxUndoManager * * Implements a command history. When changing the graph model, an * 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 * is dispatched in an event, and added to the history inside * . This is done by an event listener in * . * * Each atomic change of the model is represented by an object (eg. * , , etc) which contains the * complete undo information. The also listens to the * 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 * and using * . * * Event: mxEvent.CLEAR * * Fires after was invoked. This event has no properties. * * Event: mxEvent.UNDO * * Fires afer a significant edit was undone in . The edit * property contains the that was undone. * * Event: mxEvent.REDO * * Fires afer a significant edit was redone in . The edit * property contains the that was redone. * * Event: mxEvent.ADD * * Fires after an undoable edit was added to the history. The edit * property contains the 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 . */ 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 from the history, * invoking die on each edit. This is called from . */ 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(); } } };