1 : /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 : /* ***** BEGIN LICENSE BLOCK *****
3 : * Version: MPL 1.1/GPL 2.0/LGPL 2.1
4 : *
5 : * The contents of this file are subject to the Mozilla Public License Version
6 : * 1.1 (the "License"); you may not use this file except in compliance with
7 : * the License. You may obtain a copy of the License at
8 : * http://www.mozilla.org/MPL/
9 : *
10 : * Software distributed under the License is distributed on an "AS IS" basis,
11 : * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
12 : * for the specific language governing rights and limitations under the
13 : * License.
14 : *
15 : * The Original Code is mozilla.org code.
16 : *
17 : * The Initial Developer of the Original Code is
18 : * Netscape Communications Corporation.
19 : * Portions created by the Initial Developer are Copyright (C) 1998
20 : * the Initial Developer. All Rights Reserved.
21 : *
22 : * Contributor(s):
23 : *
24 : * Alternatively, the contents of this file may be used under the terms of
25 : * either of the GNU General Public License Version 2 or later (the "GPL"),
26 : * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
27 : * in which case the provisions of the GPL or the LGPL are applicable instead
28 : * of those above. If you wish to allow use of your version of this file only
29 : * under the terms of either the GPL or the LGPL, and not to allow others to
30 : * use your version of this file under the terms of the MPL, indicate your
31 : * decision by deleting the provisions above and replace them with the notice
32 : * and other provisions required by the GPL or the LGPL. If you do not delete
33 : * the provisions above, a recipient may use your version of this file under
34 : * the terms of any one of the MPL, the GPL or the LGPL.
35 : *
36 : * ***** END LICENSE BLOCK ***** */
37 :
38 : #include "nsITransaction.h"
39 : #include "nsITransactionListener.h"
40 :
41 : #include "nsTransactionItem.h"
42 : #include "nsTransactionStack.h"
43 : #include "nsVoidArray.h"
44 : #include "nsTransactionManager.h"
45 : #include "nsTransactionList.h"
46 : #include "nsAutoPtr.h"
47 : #include "nsCOMPtr.h"
48 :
49 9 : nsTransactionManager::nsTransactionManager(PRInt32 aMaxTransactionCount)
50 : : mMaxTransactionCount(aMaxTransactionCount)
51 : , mDoStack(nsTransactionStack::FOR_UNDO)
52 : , mUndoStack(nsTransactionStack::FOR_UNDO)
53 9 : , mRedoStack(nsTransactionStack::FOR_REDO)
54 : {
55 9 : }
56 :
57 18 : nsTransactionManager::~nsTransactionManager()
58 : {
59 36 : }
60 :
61 1464 : NS_IMPL_CYCLE_COLLECTION_CLASS(nsTransactionManager)
62 :
63 0 : NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsTransactionManager)
64 0 : NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMARRAY(mListeners)
65 0 : tmp->mDoStack.DoUnlink();
66 0 : tmp->mUndoStack.DoUnlink();
67 0 : tmp->mRedoStack.DoUnlink();
68 0 : NS_IMPL_CYCLE_COLLECTION_UNLINK_END
69 :
70 2 : NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsTransactionManager)
71 2 : NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMARRAY(mListeners)
72 2 : tmp->mDoStack.DoTraverse(cb);
73 2 : tmp->mUndoStack.DoTraverse(cb);
74 2 : tmp->mRedoStack.DoTraverse(cb);
75 2 : NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
76 :
77 356 : NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsTransactionManager)
78 232 : NS_INTERFACE_MAP_ENTRY(nsITransactionManager)
79 214 : NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
80 214 : NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsITransactionManager)
81 112 : NS_INTERFACE_MAP_END
82 :
83 129 : NS_IMPL_CYCLE_COLLECTING_ADDREF(nsTransactionManager)
84 129 : NS_IMPL_CYCLE_COLLECTING_RELEASE(nsTransactionManager)
85 :
86 : NS_IMETHODIMP
87 4289 : nsTransactionManager::DoTransaction(nsITransaction *aTransaction)
88 : {
89 : nsresult result;
90 :
91 4289 : NS_ENSURE_TRUE(aTransaction, NS_ERROR_NULL_POINTER);
92 :
93 4287 : bool doInterrupt = false;
94 :
95 4287 : result = WillDoNotify(aTransaction, &doInterrupt);
96 :
97 4287 : if (NS_FAILED(result)) {
98 0 : return result;
99 : }
100 :
101 4287 : if (doInterrupt) {
102 0 : return NS_OK;
103 : }
104 :
105 4287 : result = BeginTransaction(aTransaction);
106 :
107 4287 : if (NS_FAILED(result)) {
108 8 : DidDoNotify(aTransaction, result);
109 8 : return result;
110 : }
111 :
112 4279 : result = EndTransaction();
113 :
114 4279 : nsresult result2 = DidDoNotify(aTransaction, result);
115 :
116 4279 : if (NS_SUCCEEDED(result))
117 4279 : result = result2;
118 :
119 4279 : return result;
120 : }
121 :
122 : NS_IMETHODIMP
123 429 : nsTransactionManager::UndoTransaction()
124 : {
125 429 : nsresult result = NS_OK;
126 :
127 : // It is illegal to call UndoTransaction() while the transaction manager is
128 : // executing a transaction's DoTransaction() method! If this happens,
129 : // the UndoTransaction() request is ignored, and we return NS_ERROR_FAILURE.
130 :
131 858 : nsRefPtr<nsTransactionItem> tx = mDoStack.Peek();
132 :
133 429 : if (tx) {
134 0 : return NS_ERROR_FAILURE;
135 : }
136 :
137 : // Peek at the top of the undo stack. Don't remove the transaction
138 : // until it has successfully completed.
139 429 : tx = mUndoStack.Peek();
140 :
141 : // Bail if there's nothing on the stack.
142 429 : if (!tx) {
143 2 : return NS_OK;
144 : }
145 :
146 854 : nsCOMPtr<nsITransaction> t;
147 :
148 427 : result = tx->GetTransaction(getter_AddRefs(t));
149 :
150 427 : if (NS_FAILED(result)) {
151 0 : return result;
152 : }
153 :
154 427 : bool doInterrupt = false;
155 :
156 427 : result = WillUndoNotify(t, &doInterrupt);
157 :
158 427 : if (NS_FAILED(result)) {
159 0 : return result;
160 : }
161 :
162 427 : if (doInterrupt) {
163 0 : return NS_OK;
164 : }
165 :
166 427 : result = tx->UndoTransaction(this);
167 :
168 427 : if (NS_SUCCEEDED(result)) {
169 423 : tx = mUndoStack.Pop();
170 423 : mRedoStack.Push(tx);
171 : }
172 :
173 427 : nsresult result2 = DidUndoNotify(t, result);
174 :
175 427 : if (NS_SUCCEEDED(result))
176 423 : result = result2;
177 :
178 427 : return result;
179 : }
180 :
181 : NS_IMETHODIMP
182 179 : nsTransactionManager::RedoTransaction()
183 : {
184 179 : nsresult result = NS_OK;
185 :
186 : // It is illegal to call RedoTransaction() while the transaction manager is
187 : // executing a transaction's DoTransaction() method! If this happens,
188 : // the RedoTransaction() request is ignored, and we return NS_ERROR_FAILURE.
189 :
190 358 : nsRefPtr<nsTransactionItem> tx = mDoStack.Peek();
191 :
192 179 : if (tx) {
193 0 : return NS_ERROR_FAILURE;
194 : }
195 :
196 : // Peek at the top of the redo stack. Don't remove the transaction
197 : // until it has successfully completed.
198 179 : tx = mRedoStack.Peek();
199 :
200 : // Bail if there's nothing on the stack.
201 179 : if (!tx) {
202 2 : return NS_OK;
203 : }
204 :
205 354 : nsCOMPtr<nsITransaction> t;
206 :
207 177 : result = tx->GetTransaction(getter_AddRefs(t));
208 :
209 177 : if (NS_FAILED(result)) {
210 0 : return result;
211 : }
212 :
213 177 : bool doInterrupt = false;
214 :
215 177 : result = WillRedoNotify(t, &doInterrupt);
216 :
217 177 : if (NS_FAILED(result)) {
218 0 : return result;
219 : }
220 :
221 177 : if (doInterrupt) {
222 0 : return NS_OK;
223 : }
224 :
225 177 : result = tx->RedoTransaction(this);
226 :
227 177 : if (NS_SUCCEEDED(result)) {
228 173 : tx = mRedoStack.Pop();
229 173 : mUndoStack.Push(tx);
230 : }
231 :
232 177 : nsresult result2 = DidRedoNotify(t, result);
233 :
234 177 : if (NS_SUCCEEDED(result))
235 173 : result = result2;
236 :
237 177 : return result;
238 : }
239 :
240 : NS_IMETHODIMP
241 15 : nsTransactionManager::Clear()
242 : {
243 : nsresult result;
244 :
245 15 : result = ClearRedoStack();
246 :
247 15 : if (NS_FAILED(result)) {
248 0 : return result;
249 : }
250 :
251 15 : result = ClearUndoStack();
252 :
253 15 : return result;
254 : }
255 :
256 : NS_IMETHODIMP
257 698 : nsTransactionManager::BeginBatch()
258 : {
259 : nsresult result;
260 :
261 : // We can batch independent transactions together by simply pushing
262 : // a dummy transaction item on the do stack. This dummy transaction item
263 : // will be popped off the do stack, and then pushed on the undo stack
264 : // in EndBatch().
265 :
266 698 : bool doInterrupt = false;
267 :
268 698 : result = WillBeginBatchNotify(&doInterrupt);
269 :
270 698 : if (NS_FAILED(result)) {
271 0 : return result;
272 : }
273 :
274 698 : if (doInterrupt) {
275 0 : return NS_OK;
276 : }
277 :
278 698 : result = BeginTransaction(0);
279 :
280 698 : nsresult result2 = DidBeginBatchNotify(result);
281 :
282 698 : if (NS_SUCCEEDED(result))
283 698 : result = result2;
284 :
285 698 : return result;
286 : }
287 :
288 : NS_IMETHODIMP
289 702 : nsTransactionManager::EndBatch()
290 : {
291 1404 : nsCOMPtr<nsITransaction> ti;
292 : nsresult result;
293 :
294 : // XXX: Need to add some mechanism to detect the case where the transaction
295 : // at the top of the do stack isn't the dummy transaction, so we can
296 : // throw an error!! This can happen if someone calls EndBatch() within
297 : // the DoTransaction() method of a transaction.
298 : //
299 : // For now, we can detect this case by checking the value of the
300 : // dummy transaction's mTransaction field. If it is our dummy
301 : // transaction, it should be NULL. This may not be true in the
302 : // future when we allow users to execute a transaction when beginning
303 : // a batch!!!!
304 :
305 1404 : nsRefPtr<nsTransactionItem> tx = mDoStack.Peek();
306 :
307 702 : if (tx)
308 698 : tx->GetTransaction(getter_AddRefs(ti));
309 :
310 702 : if (!tx || ti) {
311 4 : return NS_ERROR_FAILURE;
312 : }
313 :
314 698 : bool doInterrupt = false;
315 :
316 698 : result = WillEndBatchNotify(&doInterrupt);
317 :
318 698 : if (NS_FAILED(result)) {
319 0 : return result;
320 : }
321 :
322 698 : if (doInterrupt) {
323 0 : return NS_OK;
324 : }
325 :
326 698 : result = EndTransaction();
327 :
328 698 : nsresult result2 = DidEndBatchNotify(result);
329 :
330 698 : if (NS_SUCCEEDED(result))
331 698 : result = result2;
332 :
333 698 : return result;
334 : }
335 :
336 : NS_IMETHODIMP
337 290 : nsTransactionManager::GetNumberOfUndoItems(PRInt32 *aNumItems)
338 : {
339 290 : *aNumItems = mUndoStack.GetSize();
340 290 : return NS_OK;
341 : }
342 :
343 : NS_IMETHODIMP
344 268 : nsTransactionManager::GetNumberOfRedoItems(PRInt32 *aNumItems)
345 : {
346 268 : *aNumItems = mRedoStack.GetSize();
347 268 : return NS_OK;
348 : }
349 :
350 : NS_IMETHODIMP
351 0 : nsTransactionManager::GetMaxTransactionCount(PRInt32 *aMaxCount)
352 : {
353 0 : NS_ENSURE_TRUE(aMaxCount, NS_ERROR_NULL_POINTER);
354 :
355 0 : *aMaxCount = mMaxTransactionCount;
356 :
357 0 : return NS_OK;
358 : }
359 :
360 : NS_IMETHODIMP
361 24 : nsTransactionManager::SetMaxTransactionCount(PRInt32 aMaxCount)
362 : {
363 24 : PRInt32 numUndoItems = 0, numRedoItems = 0, total = 0;
364 :
365 : // It is illegal to call SetMaxTransactionCount() while the transaction
366 : // manager is executing a transaction's DoTransaction() method because
367 : // the undo and redo stacks might get pruned! If this happens, the
368 : // SetMaxTransactionCount() request is ignored, and we return
369 : // NS_ERROR_FAILURE.
370 :
371 48 : nsRefPtr<nsTransactionItem> tx = mDoStack.Peek();
372 :
373 24 : if (tx) {
374 0 : return NS_ERROR_FAILURE;
375 : }
376 :
377 : // If aMaxCount is less than zero, the user wants unlimited
378 : // levels of undo! No need to prune the undo or redo stacks!
379 :
380 24 : if (aMaxCount < 0) {
381 8 : mMaxTransactionCount = -1;
382 8 : return NS_OK;
383 : }
384 :
385 16 : numUndoItems = mUndoStack.GetSize();
386 :
387 16 : numRedoItems = mRedoStack.GetSize();
388 :
389 16 : total = numUndoItems + numRedoItems;
390 :
391 : // If aMaxCount is greater than the number of transactions that currently
392 : // exist on the undo and redo stack, there is no need to prune the
393 : // undo or redo stacks!
394 :
395 16 : if (aMaxCount > total ) {
396 6 : mMaxTransactionCount = aMaxCount;
397 6 : return NS_OK;
398 : }
399 :
400 : // Try getting rid of some transactions on the undo stack! Start at
401 : // the bottom of the stack and pop towards the top.
402 :
403 56 : while (numUndoItems > 0 && (numRedoItems + numUndoItems) > aMaxCount) {
404 36 : tx = mUndoStack.PopBottom();
405 :
406 36 : if (!tx) {
407 0 : return NS_ERROR_FAILURE;
408 : }
409 :
410 36 : --numUndoItems;
411 : }
412 :
413 : // If necessary, get rid of some transactions on the redo stack! Start at
414 : // the bottom of the stack and pop towards the top.
415 :
416 38 : while (numRedoItems > 0 && (numRedoItems + numUndoItems) > aMaxCount) {
417 18 : tx = mRedoStack.PopBottom();
418 :
419 18 : if (!tx) {
420 0 : return NS_ERROR_FAILURE;
421 : }
422 :
423 18 : --numRedoItems;
424 : }
425 :
426 10 : mMaxTransactionCount = aMaxCount;
427 :
428 10 : return NS_OK;
429 : }
430 :
431 : NS_IMETHODIMP
432 50 : nsTransactionManager::PeekUndoStack(nsITransaction **aTransaction)
433 : {
434 : nsresult result;
435 :
436 50 : NS_ENSURE_TRUE(aTransaction, NS_ERROR_NULL_POINTER);
437 :
438 50 : *aTransaction = 0;
439 :
440 100 : nsRefPtr<nsTransactionItem> tx = mUndoStack.Peek();
441 :
442 50 : if (!tx) {
443 4 : return NS_OK;
444 : }
445 :
446 46 : result = tx->GetTransaction(aTransaction);
447 :
448 46 : return result;
449 : }
450 :
451 : NS_IMETHODIMP
452 50 : nsTransactionManager::PeekRedoStack(nsITransaction **aTransaction)
453 : {
454 : nsresult result;
455 :
456 50 : NS_ENSURE_TRUE(aTransaction, NS_ERROR_NULL_POINTER);
457 :
458 50 : *aTransaction = 0;
459 :
460 100 : nsRefPtr<nsTransactionItem> tx = mRedoStack.Peek();
461 :
462 50 : if (!tx) {
463 26 : return NS_OK;
464 : }
465 :
466 24 : result = tx->GetTransaction(aTransaction);
467 :
468 24 : return result;
469 : }
470 :
471 : NS_IMETHODIMP
472 0 : nsTransactionManager::GetUndoList(nsITransactionList **aTransactionList)
473 : {
474 0 : NS_ENSURE_TRUE(aTransactionList, NS_ERROR_NULL_POINTER);
475 :
476 0 : *aTransactionList = (nsITransactionList *)new nsTransactionList(this, &mUndoStack);
477 :
478 0 : NS_IF_ADDREF(*aTransactionList);
479 :
480 0 : return (! *aTransactionList) ? NS_ERROR_OUT_OF_MEMORY : NS_OK;
481 : }
482 :
483 : NS_IMETHODIMP
484 0 : nsTransactionManager::GetRedoList(nsITransactionList **aTransactionList)
485 : {
486 0 : NS_ENSURE_TRUE(aTransactionList, NS_ERROR_NULL_POINTER);
487 :
488 0 : *aTransactionList = (nsITransactionList *)new nsTransactionList(this, &mRedoStack);
489 :
490 0 : NS_IF_ADDREF(*aTransactionList);
491 :
492 0 : return (! *aTransactionList) ? NS_ERROR_OUT_OF_MEMORY : NS_OK;
493 : }
494 :
495 : NS_IMETHODIMP
496 4 : nsTransactionManager::AddListener(nsITransactionListener *aListener)
497 : {
498 4 : NS_ENSURE_TRUE(aListener, NS_ERROR_NULL_POINTER);
499 :
500 2 : return mListeners.AppendObject(aListener) ? NS_OK : NS_ERROR_FAILURE;
501 : }
502 :
503 : NS_IMETHODIMP
504 4 : nsTransactionManager::RemoveListener(nsITransactionListener *aListener)
505 : {
506 4 : NS_ENSURE_TRUE(aListener, NS_ERROR_NULL_POINTER);
507 :
508 2 : return mListeners.RemoveObject(aListener) ? NS_OK : NS_ERROR_FAILURE;
509 : }
510 :
511 : nsresult
512 15 : nsTransactionManager::ClearUndoStack()
513 : {
514 15 : mUndoStack.Clear();
515 15 : return NS_OK;
516 : }
517 :
518 : nsresult
519 435 : nsTransactionManager::ClearRedoStack()
520 : {
521 435 : mRedoStack.Clear();
522 435 : return NS_OK;
523 : }
524 :
525 : nsresult
526 4287 : nsTransactionManager::WillDoNotify(nsITransaction *aTransaction, bool *aInterrupt)
527 : {
528 4287 : nsresult result = NS_OK;
529 4313 : for (PRInt32 i = 0, lcount = mListeners.Count(); i < lcount; i++)
530 : {
531 26 : nsITransactionListener *listener = mListeners[i];
532 :
533 26 : NS_ENSURE_TRUE(listener, NS_ERROR_FAILURE);
534 :
535 26 : result = listener->WillDo(this, aTransaction, aInterrupt);
536 :
537 26 : if (NS_FAILED(result) || *aInterrupt)
538 0 : break;
539 : }
540 :
541 4287 : return result;
542 : }
543 :
544 : nsresult
545 4287 : nsTransactionManager::DidDoNotify(nsITransaction *aTransaction, nsresult aDoResult)
546 : {
547 4287 : nsresult result = NS_OK;
548 4313 : for (PRInt32 i = 0, lcount = mListeners.Count(); i < lcount; i++)
549 : {
550 26 : nsITransactionListener *listener = mListeners[i];
551 :
552 26 : NS_ENSURE_TRUE(listener, NS_ERROR_FAILURE);
553 :
554 26 : result = listener->DidDo(this, aTransaction, aDoResult);
555 :
556 26 : if (NS_FAILED(result))
557 0 : break;
558 : }
559 :
560 4287 : return result;
561 : }
562 :
563 : nsresult
564 6228 : nsTransactionManager::WillUndoNotify(nsITransaction *aTransaction, bool *aInterrupt)
565 : {
566 6228 : nsresult result = NS_OK;
567 6230 : for (PRInt32 i = 0, lcount = mListeners.Count(); i < lcount; i++)
568 : {
569 2 : nsITransactionListener *listener = mListeners[i];
570 :
571 2 : NS_ENSURE_TRUE(listener, NS_ERROR_FAILURE);
572 :
573 2 : result = listener->WillUndo(this, aTransaction, aInterrupt);
574 :
575 2 : if (NS_FAILED(result) || *aInterrupt)
576 0 : break;
577 : }
578 :
579 6228 : return result;
580 : }
581 :
582 : nsresult
583 8998 : nsTransactionManager::DidUndoNotify(nsITransaction *aTransaction, nsresult aUndoResult)
584 : {
585 8998 : nsresult result = NS_OK;
586 9000 : for (PRInt32 i = 0, lcount = mListeners.Count(); i < lcount; i++)
587 : {
588 2 : nsITransactionListener *listener = mListeners[i];
589 :
590 2 : NS_ENSURE_TRUE(listener, NS_ERROR_FAILURE);
591 :
592 2 : result = listener->DidUndo(this, aTransaction, aUndoResult);
593 :
594 2 : if (NS_FAILED(result))
595 0 : break;
596 : }
597 :
598 8998 : return result;
599 : }
600 :
601 : nsresult
602 2947 : nsTransactionManager::WillRedoNotify(nsITransaction *aTransaction, bool *aInterrupt)
603 : {
604 2947 : nsresult result = NS_OK;
605 2947 : for (PRInt32 i = 0, lcount = mListeners.Count(); i < lcount; i++)
606 : {
607 0 : nsITransactionListener *listener = mListeners[i];
608 :
609 0 : NS_ENSURE_TRUE(listener, NS_ERROR_FAILURE);
610 :
611 0 : result = listener->WillRedo(this, aTransaction, aInterrupt);
612 :
613 0 : if (NS_FAILED(result) || *aInterrupt)
614 0 : break;
615 : }
616 :
617 2947 : return result;
618 : }
619 :
620 : nsresult
621 177 : nsTransactionManager::DidRedoNotify(nsITransaction *aTransaction, nsresult aRedoResult)
622 : {
623 177 : nsresult result = NS_OK;
624 177 : for (PRInt32 i = 0, lcount = mListeners.Count(); i < lcount; i++)
625 : {
626 0 : nsITransactionListener *listener = mListeners[i];
627 :
628 0 : NS_ENSURE_TRUE(listener, NS_ERROR_FAILURE);
629 :
630 0 : result = listener->DidRedo(this, aTransaction, aRedoResult);
631 :
632 0 : if (NS_FAILED(result))
633 0 : break;
634 : }
635 :
636 177 : return result;
637 : }
638 :
639 : nsresult
640 698 : nsTransactionManager::WillBeginBatchNotify(bool *aInterrupt)
641 : {
642 698 : nsresult result = NS_OK;
643 699 : for (PRInt32 i = 0, lcount = mListeners.Count(); i < lcount; i++)
644 : {
645 1 : nsITransactionListener *listener = mListeners[i];
646 :
647 1 : NS_ENSURE_TRUE(listener, NS_ERROR_FAILURE);
648 :
649 1 : result = listener->WillBeginBatch(this, aInterrupt);
650 :
651 1 : if (NS_FAILED(result) || *aInterrupt)
652 0 : break;
653 : }
654 :
655 698 : return result;
656 : }
657 :
658 : nsresult
659 698 : nsTransactionManager::DidBeginBatchNotify(nsresult aResult)
660 : {
661 698 : nsresult result = NS_OK;
662 699 : for (PRInt32 i = 0, lcount = mListeners.Count(); i < lcount; i++)
663 : {
664 1 : nsITransactionListener *listener = mListeners[i];
665 :
666 1 : NS_ENSURE_TRUE(listener, NS_ERROR_FAILURE);
667 :
668 1 : result = listener->DidBeginBatch(this, aResult);
669 :
670 1 : if (NS_FAILED(result))
671 0 : break;
672 : }
673 :
674 698 : return result;
675 : }
676 :
677 : nsresult
678 698 : nsTransactionManager::WillEndBatchNotify(bool *aInterrupt)
679 : {
680 698 : nsresult result = NS_OK;
681 699 : for (PRInt32 i = 0, lcount = mListeners.Count(); i < lcount; i++)
682 : {
683 1 : nsITransactionListener *listener = mListeners[i];
684 :
685 1 : NS_ENSURE_TRUE(listener, NS_ERROR_FAILURE);
686 :
687 1 : result = listener->WillEndBatch(this, aInterrupt);
688 :
689 1 : if (NS_FAILED(result) || *aInterrupt)
690 0 : break;
691 : }
692 :
693 698 : return result;
694 : }
695 :
696 : nsresult
697 698 : nsTransactionManager::DidEndBatchNotify(nsresult aResult)
698 : {
699 698 : nsresult result = NS_OK;
700 699 : for (PRInt32 i = 0, lcount = mListeners.Count(); i < lcount; i++)
701 : {
702 1 : nsITransactionListener *listener = mListeners[i];
703 :
704 1 : NS_ENSURE_TRUE(listener, NS_ERROR_FAILURE);
705 :
706 1 : result = listener->DidEndBatch(this, aResult);
707 :
708 1 : if (NS_FAILED(result))
709 0 : break;
710 : }
711 :
712 698 : return result;
713 : }
714 :
715 : nsresult
716 325 : nsTransactionManager::WillMergeNotify(nsITransaction *aTop, nsITransaction *aTransaction, bool *aInterrupt)
717 : {
718 325 : nsresult result = NS_OK;
719 345 : for (PRInt32 i = 0, lcount = mListeners.Count(); i < lcount; i++)
720 : {
721 20 : nsITransactionListener *listener = mListeners[i];
722 :
723 20 : NS_ENSURE_TRUE(listener, NS_ERROR_FAILURE);
724 :
725 20 : result = listener->WillMerge(this, aTop, aTransaction, aInterrupt);
726 :
727 20 : if (NS_FAILED(result) || *aInterrupt)
728 0 : break;
729 : }
730 :
731 325 : return result;
732 : }
733 :
734 : nsresult
735 325 : nsTransactionManager::DidMergeNotify(nsITransaction *aTop,
736 : nsITransaction *aTransaction,
737 : bool aDidMerge,
738 : nsresult aMergeResult)
739 : {
740 325 : nsresult result = NS_OK;
741 345 : for (PRInt32 i = 0, lcount = mListeners.Count(); i < lcount; i++)
742 : {
743 20 : nsITransactionListener *listener = mListeners[i];
744 :
745 20 : NS_ENSURE_TRUE(listener, NS_ERROR_FAILURE);
746 :
747 20 : result = listener->DidMerge(this, aTop, aTransaction, aDidMerge, aMergeResult);
748 :
749 20 : if (NS_FAILED(result))
750 0 : break;
751 : }
752 :
753 325 : return result;
754 : }
755 :
756 : nsresult
757 4985 : nsTransactionManager::BeginTransaction(nsITransaction *aTransaction)
758 : {
759 4985 : nsresult result = NS_OK;
760 :
761 : // XXX: POSSIBLE OPTIMIZATION
762 : // We could use a factory that pre-allocates/recycles transaction items.
763 9970 : nsRefPtr<nsTransactionItem> tx = new nsTransactionItem(aTransaction);
764 :
765 4985 : if (!tx) {
766 0 : return NS_ERROR_OUT_OF_MEMORY;
767 : }
768 :
769 4985 : mDoStack.Push(tx);
770 :
771 4985 : result = tx->DoTransaction();
772 :
773 4985 : if (NS_FAILED(result)) {
774 8 : tx = mDoStack.Pop();
775 8 : return result;
776 : }
777 :
778 4977 : return NS_OK;
779 : }
780 :
781 : nsresult
782 4977 : nsTransactionManager::EndTransaction()
783 : {
784 9954 : nsCOMPtr<nsITransaction> tint;
785 4977 : nsresult result = NS_OK;
786 :
787 9954 : nsRefPtr<nsTransactionItem> tx = mDoStack.Pop();
788 :
789 4977 : if (!tx)
790 0 : return NS_ERROR_FAILURE;
791 :
792 4977 : result = tx->GetTransaction(getter_AddRefs(tint));
793 :
794 4977 : if (NS_FAILED(result)) {
795 : // XXX: What do we do with the transaction item at this point?
796 0 : return result;
797 : }
798 :
799 4977 : if (!tint) {
800 698 : PRInt32 nc = 0;
801 :
802 : // If we get here, the transaction must be a dummy batch transaction
803 : // created by BeginBatch(). If it contains no children, get rid of it!
804 :
805 698 : tx->GetNumberOfChildren(&nc);
806 :
807 698 : if (!nc) {
808 110 : return result;
809 : }
810 : }
811 :
812 : // Check if the transaction is transient. If it is, there's nothing
813 : // more to do, just return.
814 :
815 4867 : bool isTransient = false;
816 :
817 4867 : if (tint)
818 4279 : result = tint->GetIsTransient(&isTransient);
819 :
820 4867 : if (NS_FAILED(result) || isTransient || !mMaxTransactionCount) {
821 : // XXX: Should we be clearing the redo stack if the transaction
822 : // is transient and there is nothing on the do stack?
823 400 : return result;
824 : }
825 :
826 : // Check if there is a transaction on the do stack. If there is,
827 : // the current transaction is a "sub" transaction, and should
828 : // be added to the transaction at the top of the do stack.
829 :
830 8934 : nsRefPtr<nsTransactionItem> top = mDoStack.Peek();
831 4467 : if (top) {
832 4047 : result = top->AddChild(tx);
833 :
834 : // XXX: What do we do if this fails?
835 :
836 4047 : return result;
837 : }
838 :
839 : // The transaction succeeded, so clear the redo stack.
840 :
841 420 : result = ClearRedoStack();
842 :
843 420 : if (NS_FAILED(result)) {
844 : // XXX: What do we do if this fails?
845 : }
846 :
847 : // Check if we can coalesce this transaction with the one at the top
848 : // of the undo stack.
849 :
850 420 : top = mUndoStack.Peek();
851 :
852 420 : if (tint && top) {
853 327 : bool didMerge = false;
854 654 : nsCOMPtr<nsITransaction> topTransaction;
855 :
856 327 : result = top->GetTransaction(getter_AddRefs(topTransaction));
857 :
858 327 : if (topTransaction) {
859 :
860 325 : bool doInterrupt = false;
861 :
862 325 : result = WillMergeNotify(topTransaction, tint, &doInterrupt);
863 :
864 325 : NS_ENSURE_SUCCESS(result, result);
865 :
866 325 : if (!doInterrupt) {
867 325 : result = topTransaction->Merge(tint, &didMerge);
868 :
869 325 : nsresult result2 = DidMergeNotify(topTransaction, tint, didMerge, result);
870 :
871 325 : if (NS_SUCCEEDED(result))
872 325 : result = result2;
873 :
874 325 : if (NS_FAILED(result)) {
875 : // XXX: What do we do if this fails?
876 : }
877 :
878 325 : if (didMerge) {
879 40 : return result;
880 : }
881 : }
882 : }
883 : }
884 :
885 : // Check to see if we've hit the max level of undo. If so,
886 : // pop the bottom transaction off the undo stack and release it!
887 :
888 380 : PRInt32 sz = mUndoStack.GetSize();
889 :
890 380 : if (mMaxTransactionCount > 0 && sz >= mMaxTransactionCount) {
891 20 : nsRefPtr<nsTransactionItem> overflow = mUndoStack.PopBottom();
892 : }
893 :
894 : // Push the transaction on the undo stack:
895 :
896 380 : mUndoStack.Push(tx);
897 :
898 380 : return NS_OK;
899 4392 : }
900 :
|