123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335 |
- // Copyright 2008 The Closure Library Authors. All Rights Reserved.
- //
- // Licensed under the Apache License, Version 2.0 (the "License");
- // you may not use this file except in compliance with the License.
- // You may obtain a copy of the License at
- //
- // http://www.apache.org/licenses/LICENSE-2.0
- //
- // Unless required by applicable law or agreed to in writing, software
- // distributed under the License is distributed on an "AS-IS" BASIS,
- // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- // See the License for the specific language governing permissions and
- // limitations under the License.
- /**
- * @fileoverview Code for managing series of undo-redo actions in the form of
- * {@link goog.editor.plugins.UndoRedoState}s.
- *
- */
- goog.provide('goog.editor.plugins.UndoRedoManager');
- goog.provide('goog.editor.plugins.UndoRedoManager.EventType');
- goog.require('goog.editor.plugins.UndoRedoState');
- goog.require('goog.events');
- goog.require('goog.events.EventTarget');
- /**
- * Manages undo and redo operations through a series of {@code UndoRedoState}s
- * maintained on undo and redo stacks.
- *
- * @constructor
- * @extends {goog.events.EventTarget}
- */
- goog.editor.plugins.UndoRedoManager = function() {
- goog.events.EventTarget.call(this);
- /**
- * The maximum number of states on the undo stack at any time. Used to limit
- * the memory footprint of the undo-redo stack.
- * TODO(user) have a separate memory size based limit.
- * @type {number}
- * @private
- */
- this.maxUndoDepth_ = 100;
- /**
- * The undo stack.
- * @type {Array<goog.editor.plugins.UndoRedoState>}
- * @private
- */
- this.undoStack_ = [];
- /**
- * The redo stack.
- * @type {Array<goog.editor.plugins.UndoRedoState>}
- * @private
- */
- this.redoStack_ = [];
- /**
- * A queue of pending undo or redo actions. Stored as objects with two
- * properties: func and state. The func property stores the undo or redo
- * function to be called, the state property stores the state that method
- * came from.
- * @type {Array<Object>}
- * @private
- */
- this.pendingActions_ = [];
- };
- goog.inherits(goog.editor.plugins.UndoRedoManager, goog.events.EventTarget);
- /**
- * Event types for the events dispatched by undo-redo manager.
- * @enum {string}
- */
- goog.editor.plugins.UndoRedoManager.EventType = {
- /**
- * Signifies that he undo or redo stack transitioned between 0 and 1 states,
- * meaning that the ability to peform undo or redo operations has changed.
- */
- STATE_CHANGE: 'state_change',
- /**
- * Signifies that a state was just added to the undo stack. Events of this
- * type will have a {@code state} property whose value is the state that
- * was just added.
- */
- STATE_ADDED: 'state_added',
- /**
- * Signifies that the undo method of a state is about to be called.
- * Events of this type will have a {@code state} property whose value is the
- * state whose undo action is about to be performed. If the event is cancelled
- * the action does not proceed, but the state will still transition between
- * stacks.
- */
- BEFORE_UNDO: 'before_undo',
- /**
- * Signifies that the redo method of a state is about to be called.
- * Events of this type will have a {@code state} property whose value is the
- * state whose redo action is about to be performed. If the event is cancelled
- * the action does not proceed, but the state will still transition between
- * stacks.
- */
- BEFORE_REDO: 'before_redo'
- };
- /**
- * The key for the listener for the completion of the asynchronous state whose
- * undo or redo action is in progress. Null if no action is in progress.
- * @type {goog.events.Key}
- * @private
- */
- goog.editor.plugins.UndoRedoManager.prototype.inProgressActionKey_ = null;
- /**
- * Set the max undo stack depth (not the real memory usage).
- * @param {number} depth Depth of the stack.
- */
- goog.editor.plugins.UndoRedoManager.prototype.setMaxUndoDepth = function(
- depth) {
- this.maxUndoDepth_ = depth;
- };
- /**
- * Add state to the undo stack. This clears the redo stack.
- *
- * @param {goog.editor.plugins.UndoRedoState} state The state to add to the undo
- * stack.
- */
- goog.editor.plugins.UndoRedoManager.prototype.addState = function(state) {
- // TODO: is the state.equals check necessary?
- if (this.undoStack_.length == 0 ||
- !state.equals(this.undoStack_[this.undoStack_.length - 1])) {
- this.undoStack_.push(state);
- if (this.undoStack_.length > this.maxUndoDepth_) {
- this.undoStack_.shift();
- }
- // Clobber the redo stack.
- var redoLength = this.redoStack_.length;
- this.redoStack_.length = 0;
- this.dispatchEvent({
- type: goog.editor.plugins.UndoRedoManager.EventType.STATE_ADDED,
- state: state
- });
- // If the redo state had states on it, then clobbering the redo stack above
- // has caused a state change.
- if (this.undoStack_.length == 1 || redoLength) {
- this.dispatchStateChange_();
- }
- }
- };
- /**
- * Dispatches a STATE_CHANGE event with this manager as the target.
- * @private
- */
- goog.editor.plugins.UndoRedoManager.prototype.dispatchStateChange_ =
- function() {
- this.dispatchEvent(
- goog.editor.plugins.UndoRedoManager.EventType.STATE_CHANGE);
- };
- /**
- * Performs the undo operation of the state at the top of the undo stack, moving
- * that state to the top of the redo stack. If the undo stack is empty, does
- * nothing.
- */
- goog.editor.plugins.UndoRedoManager.prototype.undo = function() {
- this.shiftState_(this.undoStack_, this.redoStack_);
- };
- /**
- * Performs the redo operation of the state at the top of the redo stack, moving
- * that state to the top of the undo stack. If redo undo stack is empty, does
- * nothing.
- */
- goog.editor.plugins.UndoRedoManager.prototype.redo = function() {
- this.shiftState_(this.redoStack_, this.undoStack_);
- };
- /**
- * @return {boolean} Wether the undo stack has items on it, i.e., if it is
- * possible to perform an undo operation.
- */
- goog.editor.plugins.UndoRedoManager.prototype.hasUndoState = function() {
- return this.undoStack_.length > 0;
- };
- /**
- * @return {boolean} Wether the redo stack has items on it, i.e., if it is
- * possible to perform a redo operation.
- */
- goog.editor.plugins.UndoRedoManager.prototype.hasRedoState = function() {
- return this.redoStack_.length > 0;
- };
- /**
- * Move a state from one stack to the other, performing the appropriate undo
- * or redo action.
- *
- * @param {Array<goog.editor.plugins.UndoRedoState>} fromStack Stack to move
- * the state from.
- * @param {Array<goog.editor.plugins.UndoRedoState>} toStack Stack to move
- * the state to.
- * @private
- */
- goog.editor.plugins.UndoRedoManager.prototype.shiftState_ = function(
- fromStack, toStack) {
- if (fromStack.length) {
- var state = fromStack.pop();
- // Push the current state into the redo stack.
- toStack.push(state);
- this.addAction_({
- type: fromStack == this.undoStack_ ?
- goog.editor.plugins.UndoRedoManager.EventType.BEFORE_UNDO :
- goog.editor.plugins.UndoRedoManager.EventType.BEFORE_REDO,
- func: fromStack == this.undoStack_ ? state.undo : state.redo,
- state: state
- });
- // If either stack transitioned between 0 and 1 in size then the ability
- // to do an undo or redo has changed and we must dispatch a state change.
- if (fromStack.length == 0 || toStack.length == 1) {
- this.dispatchStateChange_();
- }
- }
- };
- /**
- * Adds an action to the queue of pending undo or redo actions. If no actions
- * are pending, immediately performs the action.
- *
- * @param {Object} action An undo or redo action. Stored as an object with two
- * properties: func and state. The func property stores the undo or redo
- * function to be called, the state property stores the state that method
- * came from.
- * @private
- */
- goog.editor.plugins.UndoRedoManager.prototype.addAction_ = function(action) {
- this.pendingActions_.push(action);
- if (this.pendingActions_.length == 1) {
- this.doAction_();
- }
- };
- /**
- * Executes the action at the front of the pending actions queue. If an action
- * is already in progress or the queue is empty, does nothing.
- * @private
- */
- goog.editor.plugins.UndoRedoManager.prototype.doAction_ = function() {
- if (this.inProgressActionKey_ || this.pendingActions_.length == 0) {
- return;
- }
- var action = this.pendingActions_.shift();
- var e = {type: action.type, state: action.state};
- if (this.dispatchEvent(e)) {
- if (action.state.isAsynchronous()) {
- this.inProgressActionKey_ = goog.events.listen(
- action.state, goog.editor.plugins.UndoRedoState.ACTION_COMPLETED,
- this.finishAction_, false, this);
- action.func.call(action.state);
- } else {
- action.func.call(action.state);
- this.doAction_();
- }
- }
- };
- /**
- * Finishes processing the current in progress action, starting the next queued
- * action if one exists.
- * @private
- */
- goog.editor.plugins.UndoRedoManager.prototype.finishAction_ = function() {
- goog.events.unlistenByKey(/** @type {number} */ (this.inProgressActionKey_));
- this.inProgressActionKey_ = null;
- this.doAction_();
- };
- /**
- * Clears the undo and redo stacks.
- */
- goog.editor.plugins.UndoRedoManager.prototype.clearHistory = function() {
- if (this.undoStack_.length > 0 || this.redoStack_.length > 0) {
- this.undoStack_.length = 0;
- this.redoStack_.length = 0;
- this.dispatchStateChange_();
- }
- };
- /**
- * @return {goog.editor.plugins.UndoRedoState|undefined} The state at the top of
- * the undo stack without removing it from the stack.
- */
- goog.editor.plugins.UndoRedoManager.prototype.undoPeek = function() {
- return this.undoStack_[this.undoStack_.length - 1];
- };
- /**
- * @return {goog.editor.plugins.UndoRedoState|undefined} The state at the top of
- * the redo stack without removing it from the stack.
- */
- goog.editor.plugins.UndoRedoManager.prototype.redoPeek = function() {
- return this.redoStack_[this.redoStack_.length - 1];
- };
|