Back to index

lightning-sunbird  0.9+nobinonly
nsTransactionManager.cpp
Go to the documentation of this file.
00001 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
00002 /* ***** BEGIN LICENSE BLOCK *****
00003  * Version: MPL 1.1/GPL 2.0/LGPL 2.1
00004  *
00005  * The contents of this file are subject to the Mozilla Public License Version
00006  * 1.1 (the "License"); you may not use this file except in compliance with
00007  * the License. You may obtain a copy of the License at
00008  * http://www.mozilla.org/MPL/
00009  *
00010  * Software distributed under the License is distributed on an "AS IS" basis,
00011  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
00012  * for the specific language governing rights and limitations under the
00013  * License.
00014  *
00015  * The Original Code is mozilla.org code.
00016  *
00017  * The Initial Developer of the Original Code is
00018  * Netscape Communications Corporation.
00019  * Portions created by the Initial Developer are Copyright (C) 1998
00020  * the Initial Developer. All Rights Reserved.
00021  *
00022  * Contributor(s):
00023  *
00024  * Alternatively, the contents of this file may be used under the terms of
00025  * either of the GNU General Public License Version 2 or later (the "GPL"),
00026  * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
00027  * in which case the provisions of the GPL or the LGPL are applicable instead
00028  * of those above. If you wish to allow use of your version of this file only
00029  * under the terms of either the GPL or the LGPL, and not to allow others to
00030  * use your version of this file under the terms of the MPL, indicate your
00031  * decision by deleting the provisions above and replace them with the notice
00032  * and other provisions required by the GPL or the LGPL. If you do not delete
00033  * the provisions above, a recipient may use your version of this file under
00034  * the terms of any one of the MPL, the GPL or the LGPL.
00035  *
00036  * ***** END LICENSE BLOCK ***** */
00037 
00038 #include "nsITransaction.h"
00039 #include "nsITransactionListener.h"
00040 
00041 #include "nsTransactionItem.h"
00042 #include "nsTransactionStack.h"
00043 #include "nsVoidArray.h"
00044 #include "nsTransactionManager.h"
00045 #include "nsTransactionList.h"
00046 
00047 #include "nsCOMPtr.h"
00048 
00049 #define LOCK_TX_MANAGER(mgr)    (mgr)->Lock()
00050 #define UNLOCK_TX_MANAGER(mgr)  (mgr)->Unlock()
00051 
00052 
00053 nsTransactionManager::nsTransactionManager(PRInt32 aMaxTransactionCount)
00054   : mMaxTransactionCount(aMaxTransactionCount), mListeners(0)
00055 {
00056   mMonitor = ::PR_NewMonitor();
00057 }
00058 
00059 nsTransactionManager::~nsTransactionManager()
00060 {
00061   if (mListeners)
00062   {
00063     PRInt32 i;
00064     nsITransactionListener *listener;
00065 
00066     for (i = 0; i < mListeners->Count(); i++)
00067     {
00068       listener = (nsITransactionListener *)mListeners->ElementAt(i);
00069       NS_IF_RELEASE(listener);
00070     }
00071 
00072     delete mListeners;
00073     mListeners = 0;
00074   }
00075 
00076   if (mMonitor)
00077   {
00078     ::PR_DestroyMonitor(mMonitor);
00079     mMonitor = 0;
00080   }
00081 }
00082 
00083 #ifdef DEBUG_TXMGR_REFCNT
00084 
00085 nsrefcnt nsTransactionManager::AddRef(void)
00086 {
00087   return ++mRefCnt;
00088 }
00089 
00090 nsrefcnt nsTransactionManager::Release(void)
00091 {
00092   NS_PRECONDITION(0 != mRefCnt, "dup release");
00093   if (--mRefCnt == 0) {
00094     NS_DELETEXPCOM(this);
00095     return 0;
00096   }
00097   return mRefCnt;
00098 }
00099 
00100 NS_IMPL_QUERY_INTERFACE2(nsTransactionManager, nsITransactionManager, nsISupportsWeakReference)
00101 
00102 #else
00103 
00104 NS_IMPL_ISUPPORTS2(nsTransactionManager, nsITransactionManager, nsISupportsWeakReference)
00105 
00106 #endif
00107 
00108 NS_IMETHODIMP
00109 nsTransactionManager::DoTransaction(nsITransaction *aTransaction)
00110 {
00111   nsresult result;
00112 
00113   if (!aTransaction)
00114     return NS_ERROR_NULL_POINTER;
00115 
00116   LOCK_TX_MANAGER(this);
00117 
00118   PRBool doInterrupt = PR_FALSE;
00119 
00120   result = WillDoNotify(aTransaction, &doInterrupt);
00121 
00122   if (NS_FAILED(result)) {
00123     UNLOCK_TX_MANAGER(this);
00124     return result;
00125   }
00126 
00127   if (doInterrupt) {
00128     UNLOCK_TX_MANAGER(this);
00129     return NS_OK;
00130   }
00131 
00132   result = BeginTransaction(aTransaction);
00133 
00134   if (NS_FAILED(result)) {
00135     DidDoNotify(aTransaction, result);
00136     UNLOCK_TX_MANAGER(this);
00137     return result;
00138   }
00139 
00140   result = EndTransaction();
00141 
00142   nsresult result2 = DidDoNotify(aTransaction, result);
00143 
00144   if (NS_SUCCEEDED(result))
00145     result = result2;
00146 
00147   UNLOCK_TX_MANAGER(this);
00148 
00149   return result;
00150 }
00151 
00152 NS_IMETHODIMP
00153 nsTransactionManager::UndoTransaction()
00154 {
00155   nsresult result       = NS_OK;
00156   nsTransactionItem *tx = 0;
00157 
00158   LOCK_TX_MANAGER(this);
00159 
00160   // It is illegal to call UndoTransaction() while the transaction manager is
00161   // executing a  transaction's DoTransaction() method! If this happens,
00162   // the UndoTransaction() request is ignored, and we return NS_ERROR_FAILURE.
00163 
00164   result = mDoStack.Peek(&tx);
00165 
00166   if (NS_FAILED(result)) {
00167     UNLOCK_TX_MANAGER(this);
00168     return result;
00169   }
00170 
00171   if (tx) {
00172     UNLOCK_TX_MANAGER(this);
00173     return NS_ERROR_FAILURE;
00174   }
00175 
00176   // Peek at the top of the undo stack. Don't remove the transaction
00177   // until it has successfully completed.
00178   result = mUndoStack.Peek(&tx);
00179 
00180   if (NS_FAILED(result)) {
00181     UNLOCK_TX_MANAGER(this);
00182     return result;
00183   }
00184 
00185   // Bail if there's nothing on the stack.
00186   if (!tx) {
00187     UNLOCK_TX_MANAGER(this);
00188     return NS_OK;
00189   }
00190 
00191   nsITransaction *t = 0;
00192 
00193   result = tx->GetTransaction(&t);
00194 
00195   if (NS_FAILED(result)) {
00196     UNLOCK_TX_MANAGER(this);
00197     return result;
00198   }
00199 
00200   PRBool doInterrupt = PR_FALSE;
00201 
00202   result = WillUndoNotify(t, &doInterrupt);
00203 
00204   if (NS_FAILED(result)) {
00205     UNLOCK_TX_MANAGER(this);
00206     return result;
00207   }
00208 
00209   if (doInterrupt) {
00210     UNLOCK_TX_MANAGER(this);
00211     return NS_OK;
00212   }
00213 
00214   result = tx->UndoTransaction(this);
00215 
00216   if (NS_SUCCEEDED(result)) {
00217     result = mUndoStack.Pop(&tx);
00218 
00219     if (NS_SUCCEEDED(result))
00220       result = mRedoStack.Push(tx);
00221   }
00222 
00223   nsresult result2 = DidUndoNotify(t, result);
00224 
00225   if (NS_SUCCEEDED(result))
00226     result = result2;
00227 
00228   UNLOCK_TX_MANAGER(this);
00229 
00230   return result;
00231 }
00232 
00233 NS_IMETHODIMP
00234 nsTransactionManager::RedoTransaction()
00235 {
00236   nsresult result       = NS_OK;
00237   nsTransactionItem *tx = 0;
00238 
00239   LOCK_TX_MANAGER(this);
00240 
00241   // It is illegal to call RedoTransaction() while the transaction manager is
00242   // executing a  transaction's DoTransaction() method! If this happens,
00243   // the RedoTransaction() request is ignored, and we return NS_ERROR_FAILURE.
00244 
00245   result = mDoStack.Peek(&tx);
00246 
00247   if (NS_FAILED(result)) {
00248     UNLOCK_TX_MANAGER(this);
00249     return result;
00250   }
00251 
00252   if (tx) {
00253     UNLOCK_TX_MANAGER(this);
00254     return NS_ERROR_FAILURE;
00255   }
00256 
00257   // Peek at the top of the redo stack. Don't remove the transaction
00258   // until it has successfully completed.
00259   result = mRedoStack.Peek(&tx);
00260 
00261   if (NS_FAILED(result)) {
00262     UNLOCK_TX_MANAGER(this);
00263     return result;
00264   }
00265 
00266   // Bail if there's nothing on the stack.
00267   if (!tx) {
00268     UNLOCK_TX_MANAGER(this);
00269     return NS_OK;
00270   }
00271 
00272   nsITransaction *t = 0;
00273 
00274   result = tx->GetTransaction(&t);
00275 
00276   if (NS_FAILED(result)) {
00277     UNLOCK_TX_MANAGER(this);
00278     return result;
00279   }
00280 
00281   PRBool doInterrupt = PR_FALSE;
00282 
00283   result = WillRedoNotify(t, &doInterrupt);
00284 
00285   if (NS_FAILED(result)) {
00286     UNLOCK_TX_MANAGER(this);
00287     return result;
00288   }
00289 
00290   if (doInterrupt) {
00291     UNLOCK_TX_MANAGER(this);
00292     return NS_OK;
00293   }
00294 
00295   result = tx->RedoTransaction(this);
00296 
00297   if (NS_SUCCEEDED(result)) {
00298     result = mRedoStack.Pop(&tx);
00299 
00300     if (NS_SUCCEEDED(result))
00301       result = mUndoStack.Push(tx);
00302   }
00303 
00304   nsresult result2 = DidRedoNotify(t, result);
00305 
00306   if (NS_SUCCEEDED(result))
00307     result = result2;
00308 
00309   UNLOCK_TX_MANAGER(this);
00310 
00311   return result;
00312 }
00313 
00314 NS_IMETHODIMP
00315 nsTransactionManager::Clear()
00316 {
00317   nsresult result;
00318 
00319   LOCK_TX_MANAGER(this);
00320 
00321   result = ClearRedoStack();
00322 
00323   if (NS_FAILED(result)) {
00324     UNLOCK_TX_MANAGER(this);
00325     return result;
00326   }
00327 
00328   result = ClearUndoStack();
00329 
00330   UNLOCK_TX_MANAGER(this);
00331 
00332   return result;
00333 }
00334 
00335 NS_IMETHODIMP
00336 nsTransactionManager::BeginBatch()
00337 {
00338   nsresult result;
00339 
00340   // We can batch independent transactions together by simply pushing
00341   // a dummy transaction item on the do stack. This dummy transaction item
00342   // will be popped off the do stack, and then pushed on the undo stack
00343   // in EndBatch().
00344 
00345   LOCK_TX_MANAGER(this);
00346 
00347   PRBool doInterrupt = PR_FALSE;
00348 
00349   result = WillBeginBatchNotify(&doInterrupt);
00350 
00351   if (NS_FAILED(result)) {
00352     UNLOCK_TX_MANAGER(this);
00353     return result;
00354   }
00355 
00356   if (doInterrupt) {
00357     UNLOCK_TX_MANAGER(this);
00358     return NS_OK;
00359   }
00360 
00361   result = BeginTransaction(0);
00362   
00363   nsresult result2 = DidBeginBatchNotify(result);
00364 
00365   if (NS_SUCCEEDED(result))
00366     result = result2;
00367 
00368   UNLOCK_TX_MANAGER(this);
00369 
00370   return result;
00371 }
00372 
00373 NS_IMETHODIMP
00374 nsTransactionManager::EndBatch()
00375 {
00376   nsTransactionItem *tx = 0;
00377   nsITransaction *ti    = 0;
00378   nsresult result;
00379 
00380   LOCK_TX_MANAGER(this);
00381 
00382   // XXX: Need to add some mechanism to detect the case where the transaction
00383   //      at the top of the do stack isn't the dummy transaction, so we can
00384   //      throw an error!! This can happen if someone calls EndBatch() within
00385   //      the DoTransaction() method of a transaction.
00386   //
00387   //      For now, we can detect this case by checking the value of the
00388   //      dummy transaction's mTransaction field. If it is our dummy
00389   //      transaction, it should be NULL. This may not be true in the
00390   //      future when we allow users to execute a transaction when beginning
00391   //      a batch!!!!
00392 
00393   result = mDoStack.Peek(&tx);
00394 
00395   if (NS_FAILED(result)) {
00396     UNLOCK_TX_MANAGER(this);
00397     return result;
00398   }
00399 
00400   if (tx)
00401     tx->GetTransaction(&ti);
00402 
00403   if (!tx || ti) {
00404     UNLOCK_TX_MANAGER(this);
00405     return NS_ERROR_FAILURE;
00406   }
00407 
00408   PRBool doInterrupt = PR_FALSE;
00409 
00410   result = WillEndBatchNotify(&doInterrupt);
00411 
00412   if (NS_FAILED(result)) {
00413     UNLOCK_TX_MANAGER(this);
00414     return result;
00415   }
00416 
00417   if (doInterrupt) {
00418     UNLOCK_TX_MANAGER(this);
00419     return NS_OK;
00420   }
00421 
00422   result = EndTransaction();
00423 
00424   nsresult result2 = DidEndBatchNotify(result);
00425 
00426   if (NS_SUCCEEDED(result))
00427     result = result2;
00428 
00429   UNLOCK_TX_MANAGER(this);
00430 
00431   return result;
00432 }
00433 
00434 NS_IMETHODIMP
00435 nsTransactionManager::GetNumberOfUndoItems(PRInt32 *aNumItems)
00436 {
00437   nsresult result;
00438 
00439   LOCK_TX_MANAGER(this);
00440   result = mUndoStack.GetSize(aNumItems);
00441   UNLOCK_TX_MANAGER(this);
00442 
00443   return result;
00444 }
00445 
00446 NS_IMETHODIMP
00447 nsTransactionManager::GetNumberOfRedoItems(PRInt32 *aNumItems)
00448 {
00449   nsresult result;
00450 
00451   LOCK_TX_MANAGER(this);
00452   result = mRedoStack.GetSize(aNumItems);
00453   UNLOCK_TX_MANAGER(this);
00454 
00455   return result;
00456 }
00457 
00458 NS_IMETHODIMP
00459 nsTransactionManager::GetMaxTransactionCount(PRInt32 *aMaxCount)
00460 {
00461   if (!aMaxCount)
00462     return NS_ERROR_NULL_POINTER;
00463 
00464   LOCK_TX_MANAGER(this);
00465   *aMaxCount = mMaxTransactionCount;
00466   UNLOCK_TX_MANAGER(this);
00467 
00468   return NS_OK;
00469 }
00470 
00471 NS_IMETHODIMP
00472 nsTransactionManager::SetMaxTransactionCount(PRInt32 aMaxCount)
00473 {
00474   PRInt32 numUndoItems  = 0, numRedoItems = 0, total = 0;
00475   nsTransactionItem *tx = 0;
00476   nsresult result;
00477 
00478   LOCK_TX_MANAGER(this);
00479 
00480   // It is illegal to call SetMaxTransactionCount() while the transaction
00481   // manager is executing a  transaction's DoTransaction() method because
00482   // the undo and redo stacks might get pruned! If this happens, the
00483   // SetMaxTransactionCount() request is ignored, and we return
00484   // NS_ERROR_FAILURE.
00485 
00486   result = mDoStack.Peek(&tx);
00487 
00488   if (NS_FAILED(result)) {
00489     UNLOCK_TX_MANAGER(this);
00490     return result;
00491   }
00492 
00493   if (tx) {
00494     UNLOCK_TX_MANAGER(this);
00495     return NS_ERROR_FAILURE;
00496   }
00497 
00498   // If aMaxCount is less than zero, the user wants unlimited
00499   // levels of undo! No need to prune the undo or redo stacks!
00500 
00501   if (aMaxCount < 0) {
00502     mMaxTransactionCount = -1;
00503     UNLOCK_TX_MANAGER(this);
00504     return result;
00505   }
00506 
00507   result = mUndoStack.GetSize(&numUndoItems);
00508 
00509   if (NS_FAILED(result)) {
00510     UNLOCK_TX_MANAGER(this);
00511     return result;
00512   }
00513 
00514   result = mRedoStack.GetSize(&numRedoItems);
00515 
00516   if (NS_FAILED(result)) {
00517     UNLOCK_TX_MANAGER(this);
00518     return result;
00519   }
00520 
00521   total = numUndoItems + numRedoItems;
00522 
00523   // If aMaxCount is greater than the number of transactions that currently
00524   // exist on the undo and redo stack, there is no need to prune the
00525   // undo or redo stacks!
00526 
00527   if (aMaxCount > total ) {
00528     mMaxTransactionCount = aMaxCount;
00529     UNLOCK_TX_MANAGER(this);
00530     return result;
00531   }
00532 
00533   // Try getting rid of some transactions on the undo stack! Start at
00534   // the bottom of the stack and pop towards the top.
00535 
00536   while (numUndoItems > 0 && (numRedoItems + numUndoItems) > aMaxCount) {
00537     tx = 0;
00538     result = mUndoStack.PopBottom(&tx);
00539 
00540     if (NS_FAILED(result) || !tx) {
00541       UNLOCK_TX_MANAGER(this);
00542       return result;
00543     }
00544 
00545     delete tx;
00546 
00547     --numUndoItems;
00548   }
00549 
00550   // If necessary, get rid of some transactions on the redo stack! Start at
00551   // the bottom of the stack and pop towards the top.
00552 
00553   while (numRedoItems > 0 && (numRedoItems + numUndoItems) > aMaxCount) {
00554     tx = 0;
00555     result = mRedoStack.PopBottom(&tx);
00556 
00557     if (NS_FAILED(result) || !tx) {
00558       UNLOCK_TX_MANAGER(this);
00559       return result;
00560     }
00561 
00562     delete tx;
00563 
00564     --numRedoItems;
00565   }
00566 
00567   mMaxTransactionCount = aMaxCount;
00568 
00569   UNLOCK_TX_MANAGER(this);
00570 
00571   return result;
00572 }
00573 
00574 NS_IMETHODIMP
00575 nsTransactionManager::PeekUndoStack(nsITransaction **aTransaction)
00576 {
00577   nsTransactionItem *tx = 0;
00578   nsresult result;
00579 
00580   if (!aTransaction)
00581     return NS_ERROR_NULL_POINTER;
00582 
00583   *aTransaction = 0;
00584 
00585   LOCK_TX_MANAGER(this);
00586 
00587   result = mUndoStack.Peek(&tx);
00588 
00589   if (NS_FAILED(result) || !tx) {
00590     UNLOCK_TX_MANAGER(this);
00591     return result;
00592   }
00593 
00594   result = tx->GetTransaction(aTransaction);
00595 
00596   UNLOCK_TX_MANAGER(this);
00597 
00598   NS_IF_ADDREF(*aTransaction);
00599 
00600   return result;
00601 }
00602 
00603 NS_IMETHODIMP
00604 nsTransactionManager::PeekRedoStack(nsITransaction **aTransaction)
00605 {
00606   nsTransactionItem *tx = 0;
00607   nsresult result;
00608 
00609   if (!aTransaction)
00610     return NS_ERROR_NULL_POINTER;
00611 
00612   *aTransaction = 0;
00613 
00614   LOCK_TX_MANAGER(this);
00615 
00616   result = mRedoStack.Peek(&tx);
00617 
00618   if (NS_FAILED(result) || !tx) {
00619     UNLOCK_TX_MANAGER(this);
00620     return result;
00621   }
00622 
00623   result = tx->GetTransaction(aTransaction);
00624 
00625   UNLOCK_TX_MANAGER(this);
00626 
00627   NS_IF_ADDREF(*aTransaction);
00628 
00629   return result;
00630 }
00631 
00632 NS_IMETHODIMP
00633 nsTransactionManager::GetUndoList(nsITransactionList **aTransactionList)
00634 {
00635   if (!aTransactionList)
00636     return NS_ERROR_NULL_POINTER;
00637 
00638   *aTransactionList = (nsITransactionList *)new nsTransactionList(this, &mUndoStack);
00639 
00640   NS_IF_ADDREF(*aTransactionList);
00641 
00642   return (! *aTransactionList) ? NS_ERROR_OUT_OF_MEMORY : NS_OK;
00643 }
00644 
00645 NS_IMETHODIMP
00646 nsTransactionManager::GetRedoList(nsITransactionList **aTransactionList)
00647 {
00648   if (!aTransactionList)
00649     return NS_ERROR_NULL_POINTER;
00650 
00651   *aTransactionList = (nsITransactionList *)new nsTransactionList(this, &mRedoStack);
00652 
00653   NS_IF_ADDREF(*aTransactionList);
00654 
00655   return (! *aTransactionList) ? NS_ERROR_OUT_OF_MEMORY : NS_OK;
00656 }
00657 
00658 NS_IMETHODIMP
00659 nsTransactionManager::AddListener(nsITransactionListener *aListener)
00660 {
00661   if (!aListener)
00662     return NS_ERROR_NULL_POINTER;
00663 
00664   LOCK_TX_MANAGER(this);
00665 
00666   if (!mListeners) {
00667     mListeners = new nsAutoVoidArray();
00668 
00669     if (!mListeners) {
00670       UNLOCK_TX_MANAGER(this);
00671       return NS_ERROR_OUT_OF_MEMORY;
00672     }
00673   }
00674 
00675   if (!mListeners->AppendElement((void *)aListener)) {
00676     UNLOCK_TX_MANAGER(this);
00677     return NS_ERROR_FAILURE;
00678   }
00679 
00680   NS_ADDREF(aListener);
00681 
00682   UNLOCK_TX_MANAGER(this);
00683 
00684   return NS_OK;
00685 }
00686 
00687 NS_IMETHODIMP
00688 nsTransactionManager::RemoveListener(nsITransactionListener *aListener)
00689 {
00690   if (!aListener)
00691     return NS_ERROR_NULL_POINTER;
00692 
00693   if (!mListeners)
00694     return NS_ERROR_FAILURE;
00695 
00696   LOCK_TX_MANAGER(this);
00697 
00698   if (!mListeners->RemoveElement((void *)aListener))
00699   {
00700     UNLOCK_TX_MANAGER(this);
00701     return NS_ERROR_FAILURE;
00702   }
00703 
00704   NS_IF_RELEASE(aListener);
00705 
00706   if (mListeners->Count() < 1)
00707   {
00708     delete mListeners;
00709     mListeners = 0;
00710   }
00711 
00712   UNLOCK_TX_MANAGER(this);
00713 
00714   return NS_OK;
00715 }
00716 
00717 nsresult
00718 nsTransactionManager::ClearUndoStack()
00719 {
00720   nsresult result;
00721 
00722   LOCK_TX_MANAGER(this);
00723   result = mUndoStack.Clear();
00724   UNLOCK_TX_MANAGER(this);
00725 
00726   return result;
00727 }
00728 
00729 nsresult
00730 nsTransactionManager::ClearRedoStack()
00731 {
00732   nsresult result;
00733 
00734   LOCK_TX_MANAGER(this);
00735   result = mRedoStack.Clear();
00736   UNLOCK_TX_MANAGER(this);
00737 
00738   return result;
00739 }
00740 
00741 nsresult
00742 nsTransactionManager::WillDoNotify(nsITransaction *aTransaction, PRBool *aInterrupt)
00743 {
00744   if (!mListeners)
00745     return NS_OK;
00746 
00747   nsresult result = NS_OK;
00748   PRInt32 i, lcount = mListeners->Count();
00749 
00750   for (i = 0; i < lcount; i++)
00751   {
00752     nsITransactionListener *listener = (nsITransactionListener *)mListeners->ElementAt(i);
00753 
00754     if (!listener)
00755       return NS_ERROR_FAILURE;
00756 
00757     result = listener->WillDo(this, aTransaction, aInterrupt);
00758     
00759     if (NS_FAILED(result) || *aInterrupt)
00760       break;
00761   }
00762 
00763   return result;
00764 }
00765 
00766 nsresult
00767 nsTransactionManager::DidDoNotify(nsITransaction *aTransaction, nsresult aDoResult)
00768 {
00769   if (!mListeners)
00770     return NS_OK;
00771 
00772   nsresult result = NS_OK;
00773   PRInt32 i, lcount = mListeners->Count();
00774 
00775   for (i = 0; i < lcount; i++)
00776   {
00777     nsITransactionListener *listener = (nsITransactionListener *)mListeners->ElementAt(i);
00778 
00779     if (!listener)
00780       return NS_ERROR_FAILURE;
00781 
00782     result = listener->DidDo(this, aTransaction, aDoResult);
00783     
00784     if (NS_FAILED(result))
00785       break;
00786   }
00787 
00788   return result;
00789 }
00790 
00791 nsresult
00792 nsTransactionManager::WillUndoNotify(nsITransaction *aTransaction, PRBool *aInterrupt)
00793 {
00794   if (!mListeners)
00795     return NS_OK;
00796 
00797   nsresult result = NS_OK;
00798   PRInt32 i, lcount = mListeners->Count();
00799 
00800   for (i = 0; i < lcount; i++)
00801   {
00802     nsITransactionListener *listener = (nsITransactionListener *)mListeners->ElementAt(i);
00803 
00804     if (!listener)
00805       return NS_ERROR_FAILURE;
00806 
00807     result = listener->WillUndo(this, aTransaction, aInterrupt);
00808     
00809     if (NS_FAILED(result) || *aInterrupt)
00810       break;
00811   }
00812 
00813   return result;
00814 }
00815 
00816 nsresult
00817 nsTransactionManager::DidUndoNotify(nsITransaction *aTransaction, nsresult aUndoResult)
00818 {
00819   if (!mListeners)
00820     return NS_OK;
00821 
00822   nsresult result = NS_OK;
00823   PRInt32 i, lcount = mListeners->Count();
00824 
00825   for (i = 0; i < lcount; i++)
00826   {
00827     nsITransactionListener *listener = (nsITransactionListener *)mListeners->ElementAt(i);
00828 
00829     if (!listener)
00830       return NS_ERROR_FAILURE;
00831 
00832     result = listener->DidUndo(this, aTransaction, aUndoResult);
00833     
00834     if (NS_FAILED(result))
00835       break;
00836   }
00837 
00838   return result;
00839 }
00840 
00841 nsresult
00842 nsTransactionManager::WillRedoNotify(nsITransaction *aTransaction, PRBool *aInterrupt)
00843 {
00844   if (!mListeners)
00845     return NS_OK;
00846 
00847   nsresult result = NS_OK;
00848   PRInt32 i, lcount = mListeners->Count();
00849 
00850   for (i = 0; i < lcount; i++)
00851   {
00852     nsITransactionListener *listener = (nsITransactionListener *)mListeners->ElementAt(i);
00853 
00854     if (!listener)
00855       return NS_ERROR_FAILURE;
00856 
00857     result = listener->WillRedo(this, aTransaction, aInterrupt);
00858     
00859     if (NS_FAILED(result) || *aInterrupt)
00860       break;
00861   }
00862 
00863   return result;
00864 }
00865 
00866 nsresult
00867 nsTransactionManager::DidRedoNotify(nsITransaction *aTransaction, nsresult aRedoResult)
00868 {
00869   if (!mListeners)
00870     return NS_OK;
00871 
00872   nsresult result = NS_OK;
00873   PRInt32 i, lcount = mListeners->Count();
00874 
00875   for (i = 0; i < lcount; i++)
00876   {
00877     nsITransactionListener *listener = (nsITransactionListener *)mListeners->ElementAt(i);
00878 
00879     if (!listener)
00880       return NS_ERROR_FAILURE;
00881 
00882     result = listener->DidRedo(this, aTransaction, aRedoResult);
00883     
00884     if (NS_FAILED(result))
00885       break;
00886   }
00887 
00888   return result;
00889 }
00890 
00891 nsresult
00892 nsTransactionManager::WillBeginBatchNotify(PRBool *aInterrupt)
00893 {
00894   if (!mListeners)
00895     return NS_OK;
00896 
00897   nsresult result = NS_OK;
00898   PRInt32 i, lcount = mListeners->Count();
00899 
00900   for (i = 0; i < lcount; i++)
00901   {
00902     nsITransactionListener *listener = (nsITransactionListener *)mListeners->ElementAt(i);
00903 
00904     if (!listener)
00905       return NS_ERROR_FAILURE;
00906 
00907     result = listener->WillBeginBatch(this, aInterrupt);
00908     
00909     if (NS_FAILED(result) || *aInterrupt)
00910       break;
00911   }
00912 
00913   return result;
00914 }
00915 
00916 nsresult
00917 nsTransactionManager::DidBeginBatchNotify(nsresult aResult)
00918 {
00919   if (!mListeners)
00920     return NS_OK;
00921 
00922   nsresult result = NS_OK;
00923   PRInt32 i, lcount = mListeners->Count();
00924 
00925   for (i = 0; i < lcount; i++)
00926   {
00927     nsITransactionListener *listener = (nsITransactionListener *)mListeners->ElementAt(i);
00928 
00929     if (!listener)
00930       return NS_ERROR_FAILURE;
00931 
00932     result = listener->DidBeginBatch(this, aResult);
00933     
00934     if (NS_FAILED(result))
00935       break;
00936   }
00937 
00938   return result;
00939 }
00940 
00941 nsresult
00942 nsTransactionManager::WillEndBatchNotify(PRBool *aInterrupt)
00943 {
00944   if (!mListeners)
00945     return NS_OK;
00946 
00947   nsresult result = NS_OK;
00948   PRInt32 i, lcount = mListeners->Count();
00949 
00950   for (i = 0; i < lcount; i++)
00951   {
00952     nsITransactionListener *listener = (nsITransactionListener *)mListeners->ElementAt(i);
00953 
00954     if (!listener)
00955       return NS_ERROR_FAILURE;
00956 
00957     result = listener->WillEndBatch(this, aInterrupt);
00958     
00959     if (NS_FAILED(result) || *aInterrupt)
00960       break;
00961   }
00962 
00963   return result;
00964 }
00965 
00966 nsresult
00967 nsTransactionManager::DidEndBatchNotify(nsresult aResult)
00968 {
00969   if (!mListeners)
00970     return NS_OK;
00971 
00972   nsresult result = NS_OK;
00973   PRInt32 i, lcount = mListeners->Count();
00974 
00975   for (i = 0; i < lcount; i++)
00976   {
00977     nsITransactionListener *listener = (nsITransactionListener *)mListeners->ElementAt(i);
00978 
00979     if (!listener)
00980       return NS_ERROR_FAILURE;
00981 
00982     result = listener->DidEndBatch(this, aResult);
00983     
00984     if (NS_FAILED(result))
00985       break;
00986   }
00987 
00988   return result;
00989 }
00990 
00991 nsresult
00992 nsTransactionManager::WillMergeNotify(nsITransaction *aTop, nsITransaction *aTransaction, PRBool *aInterrupt)
00993 {
00994   if (!mListeners)
00995     return NS_OK;
00996 
00997   nsresult result = NS_OK;
00998   PRInt32 i, lcount = mListeners->Count();
00999 
01000   for (i = 0; i < lcount; i++)
01001   {
01002     nsITransactionListener *listener = (nsITransactionListener *)mListeners->ElementAt(i);
01003 
01004     if (!listener)
01005       return NS_ERROR_FAILURE;
01006 
01007     result = listener->WillMerge(this, aTop, aTransaction, aInterrupt);
01008     
01009     if (NS_FAILED(result) || *aInterrupt)
01010       break;
01011   }
01012 
01013   return result;
01014 }
01015 
01016 nsresult
01017 nsTransactionManager::DidMergeNotify(nsITransaction *aTop,
01018                                      nsITransaction *aTransaction,
01019                                      PRBool aDidMerge,
01020                                      nsresult aMergeResult)
01021 {
01022   if (!mListeners)
01023     return NS_OK;
01024 
01025   nsresult result = NS_OK;
01026   PRInt32 i, lcount = mListeners->Count();
01027 
01028   for (i = 0; i < lcount; i++)
01029   {
01030     nsITransactionListener *listener = (nsITransactionListener *)mListeners->ElementAt(i);
01031 
01032     if (!listener)
01033       return NS_ERROR_FAILURE;
01034 
01035     result = listener->DidMerge(this, aTop, aTransaction, aDidMerge, aMergeResult);
01036     
01037     if (NS_FAILED(result))
01038       break;
01039   }
01040 
01041   return result;
01042 }
01043 
01044 nsresult
01045 nsTransactionManager::BeginTransaction(nsITransaction *aTransaction)
01046 {
01047   nsTransactionItem *tx;
01048   nsresult result = NS_OK;
01049 
01050   // No need for LOCK/UNLOCK_TX_MANAGER() calls since the calling routine
01051   // should have done this already!
01052 
01053   NS_IF_ADDREF(aTransaction);
01054 
01055   // XXX: POSSIBLE OPTIMIZATION
01056   //      We could use a factory that pre-allocates/recycles transaction items.
01057   tx = new nsTransactionItem(aTransaction);
01058 
01059   if (!tx) {
01060     NS_IF_RELEASE(aTransaction);
01061     return NS_ERROR_OUT_OF_MEMORY;
01062   }
01063 
01064   result = mDoStack.Push(tx);
01065 
01066   if (NS_FAILED(result)) {
01067     delete tx;
01068     return result;
01069   }
01070 
01071   result = tx->DoTransaction();
01072 
01073   if (NS_FAILED(result)) {
01074     mDoStack.Pop(&tx);
01075     delete tx;
01076     return result;
01077   }
01078 
01079   return NS_OK;
01080 }
01081 
01082 nsresult
01083 nsTransactionManager::EndTransaction()
01084 {
01085   nsITransaction *tint = 0;
01086   nsTransactionItem *tx        = 0;
01087   nsresult result              = NS_OK;
01088 
01089   // No need for LOCK/UNLOCK_TX_MANAGER() calls since the calling routine
01090   // should have done this already!
01091 
01092   result = mDoStack.Pop(&tx);
01093 
01094   if (NS_FAILED(result) || !tx)
01095     return result;
01096 
01097   result = tx->GetTransaction(&tint);
01098 
01099   if (NS_FAILED(result)) {
01100     // XXX: What do we do with the transaction item at this point?
01101     return result;
01102   }
01103 
01104   if (!tint) {
01105     PRInt32 nc = 0;
01106 
01107     // If we get here, the transaction must be a dummy batch transaction
01108     // created by BeginBatch(). If it contains no children, get rid of it!
01109 
01110     tx->GetNumberOfChildren(&nc);
01111 
01112     if (!nc) {
01113       delete tx;
01114       return result;
01115     }
01116   }
01117 
01118   // Check if the transaction is transient. If it is, there's nothing
01119   // more to do, just return.
01120 
01121   PRBool isTransient = PR_FALSE;
01122 
01123   if (tint)
01124     result = tint->GetIsTransient(&isTransient);
01125 
01126   if (NS_FAILED(result) || isTransient || !mMaxTransactionCount) {
01127     // XXX: Should we be clearing the redo stack if the transaction
01128     //      is transient and there is nothing on the do stack?
01129     delete tx;
01130     return result;
01131   }
01132 
01133   nsTransactionItem *top = 0;
01134 
01135   // Check if there is a transaction on the do stack. If there is,
01136   // the current transaction is a "sub" transaction, and should
01137   // be added to the transaction at the top of the do stack.
01138 
01139   result = mDoStack.Peek(&top);
01140   if (top) {
01141     result = top->AddChild(tx);
01142 
01143     // XXX: What do we do if this fails?
01144 
01145     return result;
01146   }
01147 
01148   // The transaction succeeded, so clear the redo stack.
01149 
01150   result = ClearRedoStack();
01151 
01152   if (NS_FAILED(result)) {
01153     // XXX: What do we do if this fails?
01154   }
01155 
01156   // Check if we can coalesce this transaction with the one at the top
01157   // of the undo stack.
01158 
01159   top = 0;
01160   result = mUndoStack.Peek(&top);
01161 
01162   if (tint && top) {
01163     PRBool didMerge = PR_FALSE;
01164     nsITransaction *topTransaction = 0;
01165 
01166     result = top->GetTransaction(&topTransaction);
01167 
01168     if (topTransaction) {
01169 
01170       PRBool doInterrupt = PR_FALSE;
01171 
01172       result = WillMergeNotify(topTransaction, tint, &doInterrupt);
01173 
01174       if (NS_FAILED(result))
01175         return result;
01176 
01177       if (!doInterrupt) {
01178         result = topTransaction->Merge(tint, &didMerge);
01179 
01180         nsresult result2 = DidMergeNotify(topTransaction, tint, didMerge, result);
01181 
01182         if (NS_SUCCEEDED(result))
01183           result = result2;
01184 
01185         if (NS_FAILED(result)) {
01186           // XXX: What do we do if this fails?
01187         }
01188 
01189         if (didMerge) {
01190           delete tx;
01191           return result;
01192         }
01193       }
01194     }
01195   }
01196 
01197   // Check to see if we've hit the max level of undo. If so,
01198   // pop the bottom transaction off the undo stack and release it!
01199 
01200   PRInt32 sz = 0;
01201 
01202   result = mUndoStack.GetSize(&sz);
01203 
01204   if (mMaxTransactionCount > 0 && sz >= mMaxTransactionCount) {
01205     nsTransactionItem *overflow = 0;
01206 
01207     result = mUndoStack.PopBottom(&overflow);
01208 
01209     // XXX: What do we do in the case where this fails?
01210 
01211     if (overflow)
01212       delete overflow;
01213   }
01214 
01215   // Push the transaction on the undo stack:
01216 
01217   result = mUndoStack.Push(tx);
01218 
01219   if (NS_FAILED(result)) {
01220     // XXX: What do we do in the case where a clear fails?
01221     //      Remove the transaction from the stack, and release it?
01222   }
01223 
01224   return result;
01225 }
01226 
01227 nsresult
01228 nsTransactionManager::Lock()
01229 {
01230   if (mMonitor)
01231     PR_EnterMonitor(mMonitor);
01232 
01233   return NS_OK;
01234 }
01235 
01236 nsresult
01237 nsTransactionManager::Unlock()
01238 {
01239   if (mMonitor)
01240     PR_ExitMonitor(mMonitor);
01241 
01242   return NS_OK;
01243 }
01244