1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/browser/devtools/shared/undo.js Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,206 @@ 1.4 +/* -*- Mode: Javascript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 1.5 +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ 1.6 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.7 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.8 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.9 + 1.10 +/** 1.11 + * A simple undo stack manager. 1.12 + * 1.13 + * Actions are added along with the necessary code to 1.14 + * reverse the action. 1.15 + * 1.16 + * @param function aChange Called whenever the size or position 1.17 + * of the undo stack changes, to use for updating undo-related 1.18 + * UI. 1.19 + * @param integer aMaxUndo Maximum number of undo steps. 1.20 + * defaults to 50. 1.21 + */ 1.22 +function UndoStack(aMaxUndo) 1.23 +{ 1.24 + this.maxUndo = aMaxUndo || 50; 1.25 + this._stack = []; 1.26 +} 1.27 + 1.28 +exports.UndoStack = UndoStack; 1.29 + 1.30 +UndoStack.prototype = { 1.31 + // Current index into the undo stack. Is positioned after the last 1.32 + // currently-applied change. 1.33 + _index: 0, 1.34 + 1.35 + // The current batch depth (see startBatch() for details) 1.36 + _batchDepth: 0, 1.37 + 1.38 + destroy: function Undo_destroy() 1.39 + { 1.40 + this.uninstallController(); 1.41 + delete this._stack; 1.42 + }, 1.43 + 1.44 + /** 1.45 + * Start a collection of related changes. Changes will be batched 1.46 + * together into one undo/redo item until endBatch() is called. 1.47 + * 1.48 + * Batches can be nested, in which case the outer batch will contain 1.49 + * all items from the inner batches. This allows larger user 1.50 + * actions made up of a collection of smaller actions to be 1.51 + * undone as a single action. 1.52 + */ 1.53 + startBatch: function Undo_startBatch() 1.54 + { 1.55 + if (this._batchDepth++ === 0) { 1.56 + this._batch = []; 1.57 + } 1.58 + }, 1.59 + 1.60 + /** 1.61 + * End a batch of related changes, performing its action and adding 1.62 + * it to the undo stack. 1.63 + */ 1.64 + endBatch: function Undo_endBatch() 1.65 + { 1.66 + if (--this._batchDepth > 0) { 1.67 + return; 1.68 + } 1.69 + 1.70 + // Cut off the end of the undo stack at the current index, 1.71 + // and the beginning to prevent a stack larger than maxUndo. 1.72 + let start = Math.max((this._index + 1) - this.maxUndo, 0); 1.73 + this._stack = this._stack.slice(start, this._index); 1.74 + 1.75 + let batch = this._batch; 1.76 + delete this._batch; 1.77 + let entry = { 1.78 + do: function() { 1.79 + for (let item of batch) { 1.80 + item.do(); 1.81 + } 1.82 + }, 1.83 + undo: function() { 1.84 + for (let i = batch.length - 1; i >= 0; i--) { 1.85 + batch[i].undo(); 1.86 + } 1.87 + } 1.88 + }; 1.89 + this._stack.push(entry); 1.90 + this._index = this._stack.length; 1.91 + entry.do(); 1.92 + this._change(); 1.93 + }, 1.94 + 1.95 + /** 1.96 + * Perform an action, adding it to the undo stack. 1.97 + * 1.98 + * @param function aDo Called to perform the action. 1.99 + * @param function aUndo Called to reverse the action. 1.100 + */ 1.101 + do: function Undo_do(aDo, aUndo) { 1.102 + this.startBatch(); 1.103 + this._batch.push({ do: aDo, undo: aUndo }); 1.104 + this.endBatch(); 1.105 + }, 1.106 + 1.107 + /* 1.108 + * Returns true if undo() will do anything. 1.109 + */ 1.110 + canUndo: function Undo_canUndo() 1.111 + { 1.112 + return this._index > 0; 1.113 + }, 1.114 + 1.115 + /** 1.116 + * Undo the top of the undo stack. 1.117 + * 1.118 + * @return true if an action was undone. 1.119 + */ 1.120 + undo: function Undo_canUndo() 1.121 + { 1.122 + if (!this.canUndo()) { 1.123 + return false; 1.124 + } 1.125 + this._stack[--this._index].undo(); 1.126 + this._change(); 1.127 + return true; 1.128 + }, 1.129 + 1.130 + /** 1.131 + * Returns true if redo() will do anything. 1.132 + */ 1.133 + canRedo: function Undo_canRedo() 1.134 + { 1.135 + return this._stack.length > this._index; 1.136 + }, 1.137 + 1.138 + /** 1.139 + * Redo the most recently undone action. 1.140 + * 1.141 + * @return true if an action was redone. 1.142 + */ 1.143 + redo: function Undo_canRedo() 1.144 + { 1.145 + if (!this.canRedo()) { 1.146 + return false; 1.147 + } 1.148 + this._stack[this._index++].do(); 1.149 + this._change(); 1.150 + return true; 1.151 + }, 1.152 + 1.153 + _change: function Undo__change() 1.154 + { 1.155 + if (this._controllerWindow) { 1.156 + this._controllerWindow.goUpdateCommand("cmd_undo"); 1.157 + this._controllerWindow.goUpdateCommand("cmd_redo"); 1.158 + } 1.159 + }, 1.160 + 1.161 + /** 1.162 + * ViewController implementation for undo/redo. 1.163 + */ 1.164 + 1.165 + /** 1.166 + * Install this object as a command controller. 1.167 + */ 1.168 + installController: function Undo_installController(aControllerWindow) 1.169 + { 1.170 + this._controllerWindow = aControllerWindow; 1.171 + aControllerWindow.controllers.appendController(this); 1.172 + }, 1.173 + 1.174 + /** 1.175 + * Uninstall this object from the command controller. 1.176 + */ 1.177 + uninstallController: function Undo_uninstallController() 1.178 + { 1.179 + if (!this._controllerWindow) { 1.180 + return; 1.181 + } 1.182 + this._controllerWindow.controllers.removeController(this); 1.183 + }, 1.184 + 1.185 + supportsCommand: function Undo_supportsCommand(aCommand) 1.186 + { 1.187 + return (aCommand == "cmd_undo" || 1.188 + aCommand == "cmd_redo"); 1.189 + }, 1.190 + 1.191 + isCommandEnabled: function Undo_isCommandEnabled(aCommand) 1.192 + { 1.193 + switch(aCommand) { 1.194 + case "cmd_undo": return this.canUndo(); 1.195 + case "cmd_redo": return this.canRedo(); 1.196 + }; 1.197 + return false; 1.198 + }, 1.199 + 1.200 + doCommand: function Undo_doCommand(aCommand) 1.201 + { 1.202 + switch(aCommand) { 1.203 + case "cmd_undo": return this.undo(); 1.204 + case "cmd_redo": return this.redo(); 1.205 + } 1.206 + }, 1.207 + 1.208 + onEvent: function Undo_onEvent(aEvent) {}, 1.209 +}