OLD | NEW |
---|---|
1 // Copyright (c) 2011 The Chromium Authors. All rights reserved. | 1 // Copyright (c) 2011 The Chromium Authors. All rights reserved. |
2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 /** | 5 /** |
6 * Command queue is the only way to modify images. | 6 * Command queue is the only way to modify images. |
7 * Supports undo/redo. | 7 * Supports undo/redo. |
8 * Command execution is asynchronous (callback-based). | 8 * Command execution is asynchronous (callback-based). |
9 * | |
10 * @param {Document} document Document to create canvases in. | |
11 * @param {HTMLCanvasElement} canvas The canvas with the original image. | |
12 * @param {function(callback)} saveFunction Function to save the image. | |
9 */ | 13 */ |
10 function CommandQueue(document, canvas) { | 14 function CommandQueue(document, canvas, saveFunction) { |
11 this.document_ = document; | 15 this.document_ = document; |
12 this.undo_ = []; | 16 this.undo_ = []; |
13 this.redo_ = []; | 17 this.redo_ = []; |
14 this.subscribers_ = []; | 18 this.subscribers_ = []; |
15 | 19 |
16 this.baselineImage_ = canvas; | 20 this.baselineImage_ = canvas; |
17 this.currentImage_ = canvas; | 21 this.currentImage_ = canvas; |
18 this.previousImage_ = null; | 22 this.previousImage_ = null; |
19 | 23 |
24 this.saveFunction_ = saveFunction; | |
25 | |
20 this.busy_ = false; | 26 this.busy_ = false; |
21 | 27 |
22 this.UIContext_ = {}; | 28 this.UIContext_ = {}; |
23 } | 29 } |
24 | 30 |
25 /** | 31 /** |
26 * Attach the UI elements to the command queue. | 32 * Attach the UI elements to the command queue. |
27 * Once the UI is attached the results of image manipulations are displayed. | 33 * Once the UI is attached the results of image manipulations are displayed. |
28 * | 34 * |
29 * @param {ImageView} imageView The ImageView object to display the results | 35 * @param {ImageView} imageView The ImageView object to display the results |
30 * @param {ImageEditor.Prompt} prompt | 36 * @param {ImageEditor.Prompt} prompt |
31 * @param {function(boolean)} lock Function to enable/disable buttons etc. | 37 * @param {function(boolean)} lock Function to enable/disable buttons etc. |
32 */ | 38 */ |
33 CommandQueue.prototype.attachUI = function(imageView, prompt, lock) { | 39 CommandQueue.prototype.attachUI = function(imageView, prompt, lock) { |
34 this.UIContext_ = { | 40 this.UIContext_ = { |
35 imageView: imageView, | 41 imageView: imageView, |
36 prompt: prompt, | 42 prompt: prompt, |
37 lock: lock | 43 lock: lock |
38 }; | 44 }; |
39 }; | 45 }; |
40 | 46 |
41 /** | 47 /** |
42 * Detach the UI. Further image modifications will not be displayed. | 48 * Execute the action when the queue is not busy. |
49 * @param {function} callback Callback. | |
43 */ | 50 */ |
44 CommandQueue.prototype.detachUI = function() { | 51 CommandQueue.prototype.executeWhenReady = function(callback) { |
45 // The current this.UIContext_ object might be used by the command currently | 52 if (this.isBusy()) |
46 // being executed. Null out its fields so that the command continues silently. | |
47 this.UIContext_.imageView = null; | |
48 this.UIContext_.prompt = null; | |
49 this.UIContext_.lock = null; | |
50 | |
51 // Now replace the object to guarantee that we do not interfere with the | |
52 // current command. | |
53 this.UIContext_ = {}; | |
54 }; | |
55 | |
56 /** | |
57 * Asynchronous getter. Does not return the image while the queue is busy. | |
58 */ | |
59 CommandQueue.prototype.requestCurrentImage = function(callback) { | |
60 if (this.isBusy()) { | |
61 this.subscribers_.push(callback); | 53 this.subscribers_.push(callback); |
62 } else { | 54 else |
63 var self = this; | 55 setTimeout(callback, 0); |
64 setTimeout(function() { callback(self.currentImage_) }, 0); | |
65 } | |
66 }; | 56 }; |
67 | 57 |
68 CommandQueue.prototype.isBusy = function() { return this.busy_ }; | 58 CommandQueue.prototype.isBusy = function() { return this.busy_ }; |
69 | 59 |
70 CommandQueue.prototype.setBusy_ = function(on) { | 60 CommandQueue.prototype.setBusy_ = function(on) { |
71 if (this.busy_ == on) | 61 if (this.busy_ == on) |
72 throw new Error('Inconsistent CommandQueue lock state'); | 62 throw new Error('Inconsistent CommandQueue lock state'); |
73 | 63 |
74 this.busy_ = on; | 64 this.busy_ = on; |
75 | 65 |
76 if (!on) { | 66 if (!on) { |
77 // Notify the subscribers that requested the image while the queue was busy. | 67 // Execute the actions requested while the queue was busy. |
78 while (this.subscribers_.length) { | 68 while (this.subscribers_.length) |
79 this.subscribers_.pop()(this.currentImage_); | 69 this.subscribers_.shift()(); |
80 } | |
81 } | 70 } |
82 | 71 |
83 if (this.UIContext_.lock) | 72 if (this.UIContext_.lock) |
84 this.UIContext_.lock(on); | 73 this.UIContext_.lock(on); |
85 | 74 |
86 if (on) { | 75 if (on) |
87 ImageUtil.trace.resetTimer('command-busy'); | 76 ImageUtil.trace.resetTimer('command-busy'); |
88 } else { | 77 else |
89 ImageUtil.trace.reportTimer('command-busy'); | 78 ImageUtil.trace.reportTimer('command-busy'); |
90 } | 79 }; |
80 | |
81 CommandQueue.prototype.save_ = function() { | |
dgozman
2012/05/16 16:05:40
It's hard to understand the difference between |th
Vladislav Kaznacheev
2012/05/17 09:04:40
renamed to commit_ and added a JSDoc
On 2012/05/1
| |
82 setTimeout( | |
83 this.saveFunction_, | |
84 ImageView.ANIMATION_WAIT_INTERVAL, | |
85 this.setBusy_.bind(this, false)); | |
dgozman
2012/05/16 16:05:40
Is this setBusy a parameter to saveFunction? Add a
Vladislav Kaznacheev
2012/05/17 09:04:40
Rewrote for clarity.
And yes, this assumes isBusy=
| |
91 }; | 86 }; |
92 | 87 |
93 CommandQueue.prototype.doExecute_ = function(command, uiContext, callback) { | 88 CommandQueue.prototype.doExecute_ = function(command, uiContext, callback) { |
94 if (!this.currentImage_) | 89 if (!this.currentImage_) |
95 throw new Error('Cannot operate on null image'); | 90 throw new Error('Cannot operate on null image'); |
96 | 91 |
97 // Remember one previous image so that the first undo is as fast as possible. | 92 // Remember one previous image so that the first undo is as fast as possible. |
98 this.previousImage_ = this.currentImage_; | 93 this.previousImage_ = this.currentImage_; |
99 var self = this; | 94 var self = this; |
100 command.execute( | 95 command.execute( |
(...skipping 13 matching lines...) Expand all Loading... | |
114 * @param {boolean} opt_keep_redo true if redo stack should not be cleared. | 109 * @param {boolean} opt_keep_redo true if redo stack should not be cleared. |
115 */ | 110 */ |
116 CommandQueue.prototype.execute = function(command, opt_keep_redo) { | 111 CommandQueue.prototype.execute = function(command, opt_keep_redo) { |
117 this.setBusy_(true); | 112 this.setBusy_(true); |
118 | 113 |
119 if (!opt_keep_redo) | 114 if (!opt_keep_redo) |
120 this.redo_ = []; | 115 this.redo_ = []; |
121 | 116 |
122 this.undo_.push(command); | 117 this.undo_.push(command); |
123 | 118 |
124 this.doExecute_(command, this.UIContext_, this.setBusy_.bind(this, false)); | 119 this.doExecute_(command, this.UIContext_, this.save_.bind(this)); |
125 }; | 120 }; |
126 | 121 |
127 CommandQueue.prototype.canUndo = function() { | 122 CommandQueue.prototype.canUndo = function() { |
128 return this.undo_.length != 0; | 123 return this.undo_.length != 0; |
129 }; | 124 }; |
130 | 125 |
131 CommandQueue.prototype.undo = function() { | 126 CommandQueue.prototype.undo = function() { |
132 if (!this.canUndo()) | 127 if (!this.canUndo()) |
133 throw new Error('Cannot undo'); | 128 throw new Error('Cannot undo'); |
134 | 129 |
135 this.setBusy_(true); | 130 this.setBusy_(true); |
136 | 131 |
137 var command = this.undo_.pop(); | 132 var command = this.undo_.pop(); |
138 this.redo_.push(command); | 133 this.redo_.push(command); |
139 | 134 |
140 var self = this; | 135 var self = this; |
141 | 136 |
142 function complete() { | 137 function complete() { |
143 if (self.UIContext_.imageView) { | 138 if (self.UIContext_.imageView) { |
144 command.revertView(self.currentImage_, self.UIContext_.imageView); | 139 command.revertView(self.currentImage_, self.UIContext_.imageView); |
145 } | 140 } |
146 self.setBusy_(false); | 141 self.save_(); |
147 } | 142 } |
148 | 143 |
149 if (this.previousImage_) { | 144 if (this.previousImage_) { |
150 // First undo after an execute call. | 145 // First undo after an execute call. |
151 this.currentImage_ = this.previousImage_; | 146 this.currentImage_ = this.previousImage_; |
152 this.previousImage_ = null; | 147 this.previousImage_ = null; |
153 complete(); | 148 complete(); |
154 // TODO(kaznacheev) Consider recalculating previousImage_ right here | 149 // TODO(kaznacheev) Consider recalculating previousImage_ right here |
155 // by replaying the commands in the background. | 150 // by replaying the commands in the background. |
156 } else { | 151 } else { |
(...skipping 176 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
333 | 328 |
334 function onProgressInvisible(updatedRow, rowCount) { | 329 function onProgressInvisible(updatedRow, rowCount) { |
335 if (updatedRow == rowCount) { | 330 if (updatedRow == rowCount) { |
336 callback(result); | 331 callback(result); |
337 } | 332 } |
338 } | 333 } |
339 | 334 |
340 filter.applyByStrips(result, srcCanvas, this.filter_, | 335 filter.applyByStrips(result, srcCanvas, this.filter_, |
341 uiContext.imageView ? onProgressVisible : onProgressInvisible); | 336 uiContext.imageView ? onProgressVisible : onProgressInvisible); |
342 }; | 337 }; |
OLD | NEW |