Path: blob/trunk/third_party/closure/goog/editor/plugins/undoredomanager.js
2868 views
// Copyright 2008 The Closure Library Authors. All Rights Reserved.1//2// Licensed under the Apache License, Version 2.0 (the "License");3// you may not use this file except in compliance with the License.4// You may obtain a copy of the License at5//6// http://www.apache.org/licenses/LICENSE-2.07//8// Unless required by applicable law or agreed to in writing, software9// distributed under the License is distributed on an "AS-IS" BASIS,10// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.11// See the License for the specific language governing permissions and12// limitations under the License.1314/**15* @fileoverview Code for managing series of undo-redo actions in the form of16* {@link goog.editor.plugins.UndoRedoState}s.17*18*/192021goog.provide('goog.editor.plugins.UndoRedoManager');22goog.provide('goog.editor.plugins.UndoRedoManager.EventType');2324goog.require('goog.editor.plugins.UndoRedoState');25goog.require('goog.events');26goog.require('goog.events.EventTarget');27282930/**31* Manages undo and redo operations through a series of {@code UndoRedoState}s32* maintained on undo and redo stacks.33*34* @constructor35* @extends {goog.events.EventTarget}36*/37goog.editor.plugins.UndoRedoManager = function() {38goog.events.EventTarget.call(this);3940/**41* The maximum number of states on the undo stack at any time. Used to limit42* the memory footprint of the undo-redo stack.43* TODO(user) have a separate memory size based limit.44* @type {number}45* @private46*/47this.maxUndoDepth_ = 100;4849/**50* The undo stack.51* @type {Array<goog.editor.plugins.UndoRedoState>}52* @private53*/54this.undoStack_ = [];5556/**57* The redo stack.58* @type {Array<goog.editor.plugins.UndoRedoState>}59* @private60*/61this.redoStack_ = [];6263/**64* A queue of pending undo or redo actions. Stored as objects with two65* properties: func and state. The func property stores the undo or redo66* function to be called, the state property stores the state that method67* came from.68* @type {Array<Object>}69* @private70*/71this.pendingActions_ = [];72};73goog.inherits(goog.editor.plugins.UndoRedoManager, goog.events.EventTarget);747576/**77* Event types for the events dispatched by undo-redo manager.78* @enum {string}79*/80goog.editor.plugins.UndoRedoManager.EventType = {81/**82* Signifies that he undo or redo stack transitioned between 0 and 1 states,83* meaning that the ability to peform undo or redo operations has changed.84*/85STATE_CHANGE: 'state_change',8687/**88* Signifies that a state was just added to the undo stack. Events of this89* type will have a {@code state} property whose value is the state that90* was just added.91*/92STATE_ADDED: 'state_added',9394/**95* Signifies that the undo method of a state is about to be called.96* Events of this type will have a {@code state} property whose value is the97* state whose undo action is about to be performed. If the event is cancelled98* the action does not proceed, but the state will still transition between99* stacks.100*/101BEFORE_UNDO: 'before_undo',102103/**104* Signifies that the redo method of a state is about to be called.105* Events of this type will have a {@code state} property whose value is the106* state whose redo action is about to be performed. If the event is cancelled107* the action does not proceed, but the state will still transition between108* stacks.109*/110BEFORE_REDO: 'before_redo'111};112113114/**115* The key for the listener for the completion of the asynchronous state whose116* undo or redo action is in progress. Null if no action is in progress.117* @type {goog.events.Key}118* @private119*/120goog.editor.plugins.UndoRedoManager.prototype.inProgressActionKey_ = null;121122123/**124* Set the max undo stack depth (not the real memory usage).125* @param {number} depth Depth of the stack.126*/127goog.editor.plugins.UndoRedoManager.prototype.setMaxUndoDepth = function(128depth) {129this.maxUndoDepth_ = depth;130};131132133/**134* Add state to the undo stack. This clears the redo stack.135*136* @param {goog.editor.plugins.UndoRedoState} state The state to add to the undo137* stack.138*/139goog.editor.plugins.UndoRedoManager.prototype.addState = function(state) {140// TODO: is the state.equals check necessary?141if (this.undoStack_.length == 0 ||142!state.equals(this.undoStack_[this.undoStack_.length - 1])) {143this.undoStack_.push(state);144if (this.undoStack_.length > this.maxUndoDepth_) {145this.undoStack_.shift();146}147// Clobber the redo stack.148var redoLength = this.redoStack_.length;149this.redoStack_.length = 0;150151this.dispatchEvent({152type: goog.editor.plugins.UndoRedoManager.EventType.STATE_ADDED,153state: state154});155156// If the redo state had states on it, then clobbering the redo stack above157// has caused a state change.158if (this.undoStack_.length == 1 || redoLength) {159this.dispatchStateChange_();160}161}162};163164165/**166* Dispatches a STATE_CHANGE event with this manager as the target.167* @private168*/169goog.editor.plugins.UndoRedoManager.prototype.dispatchStateChange_ =170function() {171this.dispatchEvent(172goog.editor.plugins.UndoRedoManager.EventType.STATE_CHANGE);173};174175176/**177* Performs the undo operation of the state at the top of the undo stack, moving178* that state to the top of the redo stack. If the undo stack is empty, does179* nothing.180*/181goog.editor.plugins.UndoRedoManager.prototype.undo = function() {182this.shiftState_(this.undoStack_, this.redoStack_);183};184185186/**187* Performs the redo operation of the state at the top of the redo stack, moving188* that state to the top of the undo stack. If redo undo stack is empty, does189* nothing.190*/191goog.editor.plugins.UndoRedoManager.prototype.redo = function() {192this.shiftState_(this.redoStack_, this.undoStack_);193};194195196/**197* @return {boolean} Wether the undo stack has items on it, i.e., if it is198* possible to perform an undo operation.199*/200goog.editor.plugins.UndoRedoManager.prototype.hasUndoState = function() {201return this.undoStack_.length > 0;202};203204205/**206* @return {boolean} Wether the redo stack has items on it, i.e., if it is207* possible to perform a redo operation.208*/209goog.editor.plugins.UndoRedoManager.prototype.hasRedoState = function() {210return this.redoStack_.length > 0;211};212213214/**215* Move a state from one stack to the other, performing the appropriate undo216* or redo action.217*218* @param {Array<goog.editor.plugins.UndoRedoState>} fromStack Stack to move219* the state from.220* @param {Array<goog.editor.plugins.UndoRedoState>} toStack Stack to move221* the state to.222* @private223*/224goog.editor.plugins.UndoRedoManager.prototype.shiftState_ = function(225fromStack, toStack) {226if (fromStack.length) {227var state = fromStack.pop();228229// Push the current state into the redo stack.230toStack.push(state);231232this.addAction_({233type: fromStack == this.undoStack_ ?234goog.editor.plugins.UndoRedoManager.EventType.BEFORE_UNDO :235goog.editor.plugins.UndoRedoManager.EventType.BEFORE_REDO,236func: fromStack == this.undoStack_ ? state.undo : state.redo,237state: state238});239240// If either stack transitioned between 0 and 1 in size then the ability241// to do an undo or redo has changed and we must dispatch a state change.242if (fromStack.length == 0 || toStack.length == 1) {243this.dispatchStateChange_();244}245}246};247248249/**250* Adds an action to the queue of pending undo or redo actions. If no actions251* are pending, immediately performs the action.252*253* @param {Object} action An undo or redo action. Stored as an object with two254* properties: func and state. The func property stores the undo or redo255* function to be called, the state property stores the state that method256* came from.257* @private258*/259goog.editor.plugins.UndoRedoManager.prototype.addAction_ = function(action) {260this.pendingActions_.push(action);261if (this.pendingActions_.length == 1) {262this.doAction_();263}264};265266267/**268* Executes the action at the front of the pending actions queue. If an action269* is already in progress or the queue is empty, does nothing.270* @private271*/272goog.editor.plugins.UndoRedoManager.prototype.doAction_ = function() {273if (this.inProgressActionKey_ || this.pendingActions_.length == 0) {274return;275}276277var action = this.pendingActions_.shift();278279var e = {type: action.type, state: action.state};280281if (this.dispatchEvent(e)) {282if (action.state.isAsynchronous()) {283this.inProgressActionKey_ = goog.events.listen(284action.state, goog.editor.plugins.UndoRedoState.ACTION_COMPLETED,285this.finishAction_, false, this);286action.func.call(action.state);287} else {288action.func.call(action.state);289this.doAction_();290}291}292};293294295/**296* Finishes processing the current in progress action, starting the next queued297* action if one exists.298* @private299*/300goog.editor.plugins.UndoRedoManager.prototype.finishAction_ = function() {301goog.events.unlistenByKey(/** @type {number} */ (this.inProgressActionKey_));302this.inProgressActionKey_ = null;303this.doAction_();304};305306307/**308* Clears the undo and redo stacks.309*/310goog.editor.plugins.UndoRedoManager.prototype.clearHistory = function() {311if (this.undoStack_.length > 0 || this.redoStack_.length > 0) {312this.undoStack_.length = 0;313this.redoStack_.length = 0;314this.dispatchStateChange_();315}316};317318319/**320* @return {goog.editor.plugins.UndoRedoState|undefined} The state at the top of321* the undo stack without removing it from the stack.322*/323goog.editor.plugins.UndoRedoManager.prototype.undoPeek = function() {324return this.undoStack_[this.undoStack_.length - 1];325};326327328/**329* @return {goog.editor.plugins.UndoRedoState|undefined} The state at the top of330* the redo stack without removing it from the stack.331*/332goog.editor.plugins.UndoRedoManager.prototype.redoPeek = function() {333return this.redoStack_[this.redoStack_.length - 1];334};335336337