Back to index

lightning-sunbird  0.9+nobinonly
nsTreeSelection.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 Communicator client 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  *   Dave Hyatt <hyatt@mozilla.org> (Original Author)
00024  *   Brian Ryner <bryner@brianryner.com>
00025  *   Jan Varga <varga@ku.sk>
00026  *
00027  * Alternatively, the contents of this file may be used under the terms of
00028  * either of the GNU General Public License Version 2 or later (the "GPL"),
00029  * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
00030  * in which case the provisions of the GPL or the LGPL are applicable instead
00031  * of those above. If you wish to allow use of your version of this file only
00032  * under the terms of either the GPL or the LGPL, and not to allow others to
00033  * use your version of this file under the terms of the MPL, indicate your
00034  * decision by deleting the provisions above and replace them with the notice
00035  * and other provisions required by the GPL or the LGPL. If you do not delete
00036  * the provisions above, a recipient may use your version of this file under
00037  * the terms of any one of the MPL, the GPL or the LGPL.
00038  *
00039  * ***** END LICENSE BLOCK ***** */
00040 
00041 #include "nsCOMPtr.h"
00042 #include "nsTreeSelection.h"
00043 #include "nsIBoxObject.h"
00044 #include "nsITreeBoxObject.h"
00045 #include "nsITreeView.h"
00046 #include "nsString.h"
00047 #include "nsIDOMElement.h"
00048 #include "nsIDOMClassInfo.h"
00049 #include "nsIPresShell.h"
00050 #include "nsPresContext.h"
00051 #include "nsIContent.h"
00052 #include "nsIDocument.h"
00053 #include "nsGUIEvent.h"
00054 #include "nsINameSpaceManager.h"
00055 #include "nsXULAtoms.h"
00056 #include "nsPLDOMEvent.h"
00057 
00058 // A helper class for managing our ranges of selection.
00059 struct nsTreeRange
00060 {
00061   nsTreeSelection* mSelection;
00062 
00063   nsTreeRange* mPrev;
00064   nsTreeRange* mNext;
00065 
00066   PRInt32 mMin;
00067   PRInt32 mMax;
00068 
00069   nsTreeRange(nsTreeSelection* aSel, PRInt32 aSingleVal)
00070     :mSelection(aSel), mPrev(nsnull), mNext(nsnull), mMin(aSingleVal), mMax(aSingleVal) {};
00071   nsTreeRange(nsTreeSelection* aSel, PRInt32 aMin, PRInt32 aMax) 
00072     :mSelection(aSel), mPrev(nsnull), mNext(nsnull), mMin(aMin), mMax(aMax) {};
00073 
00074   ~nsTreeRange() { delete mNext; };
00075 
00076   void Connect(nsTreeRange* aPrev = nsnull, nsTreeRange* aNext = nsnull) {
00077     if (aPrev)
00078       aPrev->mNext = this;
00079     else
00080       mSelection->mFirstRange = this;
00081 
00082     if (aNext)
00083       aNext->mPrev = this;
00084 
00085     mPrev = aPrev;
00086     mNext = aNext;
00087   };
00088 
00089   nsresult RemoveRange(PRInt32 aStart, PRInt32 aEnd) {
00090     // This should so be a loop... sigh...
00091     // We start past the range to remove, so no more to remove
00092     if (aEnd < mMin)
00093       return NS_OK;
00094     // We are the last range to be affected
00095     if (aEnd < mMax) {
00096       if (aStart <= mMin) {
00097         // Just chop the start of the range off
00098         mMin = aEnd + 1;
00099       } else {
00100         // We need to split the range
00101         nsTreeRange* range = new nsTreeRange(mSelection, aEnd + 1, mMax);
00102         if (!range)
00103           return NS_ERROR_OUT_OF_MEMORY;
00104 
00105         mMax = aStart - 1;
00106         range->Connect(this, mNext);
00107       }
00108       return NS_OK;
00109     }
00110     nsTreeRange* next = mNext;
00111     if (aStart <= mMin) {
00112       // The remove includes us, remove ourselves from the list
00113       if (mPrev)
00114         mPrev->mNext = next;
00115       else
00116         mSelection->mFirstRange = next;
00117 
00118       if (next)
00119         next->mPrev = mPrev;
00120       mPrev = mNext = nsnull;
00121       delete this;
00122     } else if (aStart <= mMax) {
00123       // Just chop the end of the range off
00124       mMax = aStart - 1;
00125     }
00126     return next ? next->RemoveRange(aStart, aEnd) : NS_OK;
00127   };
00128 
00129   nsresult Remove(PRInt32 aIndex) {
00130     if (aIndex >= mMin && aIndex <= mMax) {
00131       // We have found the range that contains us.
00132       if (mMin == mMax) {
00133         // Delete the whole range.
00134         if (mPrev)
00135           mPrev->mNext = mNext;
00136         if (mNext)
00137           mNext->mPrev = mPrev;
00138         nsTreeRange* first = mSelection->mFirstRange;
00139         if (first == this)
00140           mSelection->mFirstRange = mNext;
00141         mNext = mPrev = nsnull;
00142         delete this;
00143       }
00144       else if (aIndex == mMin)
00145         mMin++;
00146       else if (aIndex == mMax)
00147         mMax--;
00148       else {
00149         // We have to break this range.
00150         nsTreeRange* newRange = new nsTreeRange(mSelection, aIndex + 1, mMax);
00151         if (!newRange)
00152           return NS_ERROR_OUT_OF_MEMORY;
00153 
00154         newRange->Connect(this, mNext);
00155         mMax = aIndex - 1;
00156       }
00157     }
00158     else if (mNext)
00159       return mNext->Remove(aIndex);
00160 
00161     return NS_OK;
00162   };
00163 
00164   nsresult Add(PRInt32 aIndex) {
00165     if (aIndex < mMin) {
00166       // We have found a spot to insert.
00167       if (aIndex + 1 == mMin)
00168         mMin = aIndex;
00169       else if (mPrev && mPrev->mMax+1 == aIndex)
00170         mPrev->mMax = aIndex;
00171       else {
00172         // We have to create a new range.
00173         nsTreeRange* newRange = new nsTreeRange(mSelection, aIndex);
00174         if (!newRange)
00175           return NS_ERROR_OUT_OF_MEMORY;
00176 
00177         newRange->Connect(mPrev, this);
00178       }
00179     }
00180     else if (mNext)
00181       mNext->Add(aIndex);
00182     else {
00183       // Insert on to the end.
00184       if (mMax+1 == aIndex)
00185         mMax = aIndex;
00186       else {
00187         // We have to create a new range.
00188         nsTreeRange* newRange = new nsTreeRange(mSelection, aIndex);
00189         if (!newRange)
00190           return NS_ERROR_OUT_OF_MEMORY;
00191 
00192         newRange->Connect(this, nsnull);
00193       }
00194     }
00195     return NS_OK;
00196   };
00197 
00198   PRBool Contains(PRInt32 aIndex) {
00199     if (aIndex >= mMin && aIndex <= mMax)
00200       return PR_TRUE;
00201 
00202     if (mNext)
00203       return mNext->Contains(aIndex);
00204 
00205     return PR_FALSE;
00206   };
00207 
00208   PRInt32 Count() {
00209     PRInt32 total = mMax - mMin + 1;
00210     if (mNext)
00211       total += mNext->Count();
00212     return total;
00213   };
00214 
00215   void Invalidate() {
00216     mSelection->mTree->InvalidateRange(mMin, mMax);
00217     if (mNext)
00218       mNext->Invalidate();
00219   };
00220 
00221   void RemoveAllBut(PRInt32 aIndex) {
00222     if (aIndex >= mMin && aIndex <= mMax) {
00223 
00224       // Invalidate everything in this list.
00225       mSelection->mFirstRange->Invalidate();
00226 
00227       mMin = aIndex;
00228       mMax = aIndex;
00229       
00230       nsTreeRange* first = mSelection->mFirstRange;
00231       if (mPrev)
00232         mPrev->mNext = mNext;
00233       if (mNext)
00234         mNext->mPrev = mPrev;
00235       mNext = mPrev = nsnull;
00236       
00237       if (first != this) {
00238         delete mSelection->mFirstRange;
00239         mSelection->mFirstRange = this;
00240       }
00241     }
00242     else if (mNext)
00243       mNext->RemoveAllBut(aIndex);
00244   };
00245 
00246   void Insert(nsTreeRange* aRange) {
00247     if (mMin >= aRange->mMax)
00248       aRange->Connect(mPrev, this);
00249     else if (mNext)
00250       mNext->Insert(aRange);
00251     else 
00252       aRange->Connect(this, nsnull);
00253   };
00254 };
00255 
00256 nsTreeSelection::nsTreeSelection(nsITreeBoxObject* aTree)
00257 {
00258   mTree = aTree;
00259   mSuppressed = PR_FALSE;
00260   mFirstRange = nsnull;
00261   mShiftSelectPivot = -1;
00262   mCurrentIndex = -1;
00263 }
00264 
00265 nsTreeSelection::~nsTreeSelection()
00266 {
00267   delete mFirstRange;
00268 }
00269 
00270 // QueryInterface implementation for nsBoxObject
00271 NS_INTERFACE_MAP_BEGIN(nsTreeSelection)
00272   NS_INTERFACE_MAP_ENTRY(nsITreeSelection)
00273   NS_INTERFACE_MAP_ENTRY(nsISupports)
00274   NS_INTERFACE_MAP_ENTRY_DOM_CLASSINFO(TreeSelection)
00275 NS_INTERFACE_MAP_END
00276 
00277 NS_IMPL_ADDREF(nsTreeSelection)
00278 NS_IMPL_RELEASE(nsTreeSelection)
00279 
00280 NS_IMETHODIMP nsTreeSelection::GetTree(nsITreeBoxObject * *aTree)
00281 {
00282   NS_IF_ADDREF(mTree);
00283   *aTree = mTree;
00284   return NS_OK;
00285 }
00286 
00287 NS_IMETHODIMP nsTreeSelection::SetTree(nsITreeBoxObject * aTree)
00288 {
00289   if (mSelectTimer) {
00290     mSelectTimer->Cancel();
00291     mSelectTimer = nsnull;
00292   }
00293   mTree = aTree; // WEAK
00294   return NS_OK;
00295 }
00296 
00297 NS_IMETHODIMP nsTreeSelection::GetSingle(PRBool* aSingle)
00298 {
00299   nsCOMPtr<nsIBoxObject> boxObject = do_QueryInterface(mTree);
00300   nsCOMPtr<nsIDOMElement> element;
00301   boxObject->GetElement(getter_AddRefs(element));
00302   nsCOMPtr<nsIContent> content = do_QueryInterface(element);
00303   nsAutoString seltype;
00304   content->GetAttr(kNameSpaceID_None, nsXULAtoms::seltype, seltype);
00305   *aSingle = seltype.EqualsLiteral("single");
00306   return NS_OK;
00307 }
00308 
00309 NS_IMETHODIMP nsTreeSelection::IsSelected(PRInt32 aIndex, PRBool* aResult)
00310 {
00311   if (mFirstRange)
00312     *aResult = mFirstRange->Contains(aIndex);
00313   else
00314     *aResult = PR_FALSE;
00315   return NS_OK;
00316 }
00317 
00318 NS_IMETHODIMP nsTreeSelection::TimedSelect(PRInt32 aIndex, PRInt32 aMsec)
00319 {
00320   PRBool suppressSelect = mSuppressed;
00321 
00322   if (aMsec != -1)
00323     mSuppressed = PR_TRUE;
00324 
00325   nsresult rv = Select(aIndex);
00326   if (NS_FAILED(rv))
00327     return rv;
00328 
00329   if (aMsec != -1) {
00330     mSuppressed = suppressSelect;
00331     if (!mSuppressed) {
00332       if (mSelectTimer)
00333         mSelectTimer->Cancel();
00334 
00335       mSelectTimer = do_CreateInstance("@mozilla.org/timer;1");
00336       mSelectTimer->InitWithFuncCallback(SelectCallback, this, aMsec, 
00337                                          nsITimer::TYPE_ONE_SHOT);
00338     }
00339   }
00340 
00341   return NS_OK;
00342 }
00343 
00344 NS_IMETHODIMP nsTreeSelection::Select(PRInt32 aIndex)
00345 {
00346   mShiftSelectPivot = -1;
00347 
00348   SetCurrentIndex(aIndex);
00349 
00350   if (mFirstRange) {
00351     PRBool alreadySelected = mFirstRange->Contains(aIndex);
00352 
00353     if (alreadySelected) {
00354       PRInt32 count = mFirstRange->Count();
00355       if (count > 1) {
00356         // We need to deselect everything but our item.
00357         mFirstRange->RemoveAllBut(aIndex);
00358         FireOnSelectHandler();
00359       }
00360       return NS_OK;
00361     }
00362     else {
00363       // Clear out our selection.
00364       mFirstRange->Invalidate();
00365       delete mFirstRange;
00366     }
00367   }
00368 
00369   // Create our new selection.
00370   mFirstRange = new nsTreeRange(this, aIndex);
00371   if (!mFirstRange)
00372     return NS_ERROR_OUT_OF_MEMORY;
00373 
00374   mFirstRange->Invalidate();
00375 
00376   // Fire the select event
00377   FireOnSelectHandler();
00378   return NS_OK;
00379 }
00380 
00381 NS_IMETHODIMP nsTreeSelection::ToggleSelect(PRInt32 aIndex)
00382 {
00383   // There are six cases that can occur on a ToggleSelect with our
00384   // range code.
00385   // (1) A new range should be made for a selection.
00386   // (2) A single range is removed from the selection.
00387   // (3) The item is added to an existing range.
00388   // (4) The item is removed from an existing range.
00389   // (5) The addition of the item causes two ranges to be merged.
00390   // (6) The removal of the item causes two ranges to be split.
00391   mShiftSelectPivot = -1;
00392   SetCurrentIndex(aIndex);
00393 
00394   nsresult rv = NS_OK;
00395   if (!mFirstRange)
00396     Select(aIndex);
00397   else {
00398     if (!mFirstRange->Contains(aIndex)) {
00399       PRBool single;
00400       GetSingle(&single);
00401       if (!single)
00402         rv = mFirstRange->Add(aIndex);
00403     }
00404     else
00405       rv = mFirstRange->Remove(aIndex);
00406     if (NS_SUCCEEDED(rv)) {
00407       mTree->InvalidateRow(aIndex);
00408 
00409       FireOnSelectHandler();
00410     }
00411   }
00412 
00413   return rv;
00414 }
00415 
00416 NS_IMETHODIMP nsTreeSelection::RangedSelect(PRInt32 aStartIndex, PRInt32 aEndIndex, PRBool aAugment)
00417 {
00418   PRBool single;
00419   GetSingle(&single);
00420   if ((mFirstRange || (aStartIndex != aEndIndex)) && single)
00421     return NS_OK;
00422 
00423   if (!aAugment) {
00424     // Clear our selection.
00425     if (mFirstRange) {
00426         mFirstRange->Invalidate();
00427         delete mFirstRange;
00428     }
00429   }
00430 
00431   if (aStartIndex == -1) {
00432     if (mShiftSelectPivot != -1)
00433       aStartIndex = mShiftSelectPivot;
00434     else aStartIndex = mCurrentIndex;
00435   }
00436 
00437   mShiftSelectPivot = aStartIndex;
00438   SetCurrentIndex(aEndIndex);
00439   
00440   PRInt32 start = aStartIndex < aEndIndex ? aStartIndex : aEndIndex;
00441   PRInt32 end = aStartIndex < aEndIndex ? aEndIndex : aStartIndex;
00442 
00443   if (aAugment && mFirstRange) {
00444     // We need to remove all the items within our selected range from the selection,
00445     // and then we insert our new range into the list.
00446     nsresult rv = mFirstRange->RemoveRange(start, end);
00447     if (NS_FAILED(rv))
00448       return rv;
00449   }
00450 
00451   nsTreeRange* range = new nsTreeRange(this, start, end);
00452   if (!range)
00453     return NS_ERROR_OUT_OF_MEMORY;
00454 
00455   range->Invalidate();
00456 
00457   if (aAugment && mFirstRange)
00458     mFirstRange->Insert(range);
00459   else
00460     mFirstRange = range;
00461 
00462   FireOnSelectHandler();
00463 
00464   return NS_OK;
00465 }
00466 
00467 NS_IMETHODIMP nsTreeSelection::ClearRange(PRInt32 aStartIndex, PRInt32 aEndIndex)
00468 {
00469   SetCurrentIndex(aEndIndex);
00470 
00471   if (mFirstRange) {
00472     PRInt32 start = aStartIndex < aEndIndex ? aStartIndex : aEndIndex;
00473     PRInt32 end = aStartIndex < aEndIndex ? aEndIndex : aStartIndex;
00474 
00475     mFirstRange->RemoveRange(start, end);
00476 
00477     mTree->InvalidateRange(start, end);
00478   }
00479   
00480   return NS_OK;
00481 }
00482 
00483 NS_IMETHODIMP nsTreeSelection::ClearSelection()
00484 {
00485   if (mFirstRange) {
00486     mFirstRange->Invalidate();
00487     delete mFirstRange;
00488     mFirstRange = nsnull;
00489   }
00490   mShiftSelectPivot = -1;
00491 
00492   FireOnSelectHandler();
00493 
00494   return NS_OK;
00495 }
00496 
00497 NS_IMETHODIMP nsTreeSelection::InvertSelection()
00498 {
00499   return NS_ERROR_NOT_IMPLEMENTED;
00500 }
00501 
00502 NS_IMETHODIMP nsTreeSelection::SelectAll()
00503 {
00504   nsCOMPtr<nsITreeView> view;
00505   mTree->GetView(getter_AddRefs(view));
00506   if (!view)
00507     return NS_OK;
00508 
00509   PRInt32 rowCount;
00510   view->GetRowCount(&rowCount);
00511   PRBool single;
00512   GetSingle(&single);
00513   if (rowCount == 0 || (rowCount > 1 && single))
00514     return NS_OK;
00515 
00516   mShiftSelectPivot = -1;
00517 
00518   // Invalidate not necessary when clearing selection, since 
00519   // we're going to invalidate the world on the SelectAll.
00520   delete mFirstRange;
00521 
00522   mFirstRange = new nsTreeRange(this, 0, rowCount-1);
00523   mFirstRange->Invalidate();
00524 
00525   FireOnSelectHandler();
00526 
00527   return NS_OK;
00528 }
00529 
00530 NS_IMETHODIMP nsTreeSelection::GetRangeCount(PRInt32* aResult)
00531 {
00532   PRInt32 count = 0;
00533   nsTreeRange* curr = mFirstRange;
00534   while (curr) {
00535     count++;
00536     curr = curr->mNext;
00537   }
00538 
00539   *aResult = count;
00540   return NS_OK;
00541 }
00542 
00543 NS_IMETHODIMP nsTreeSelection::GetRangeAt(PRInt32 aIndex, PRInt32* aMin, PRInt32* aMax)
00544 {
00545   *aMin = *aMax = -1;
00546   PRInt32 i = -1;
00547   nsTreeRange* curr = mFirstRange;
00548   while (curr) {
00549     i++;
00550     if (i == aIndex) {
00551       *aMin = curr->mMin;
00552       *aMax = curr->mMax;
00553       break;
00554     }
00555     curr = curr->mNext;
00556   }
00557 
00558   return NS_OK;
00559 }
00560 
00561 NS_IMETHODIMP nsTreeSelection::GetCount(PRInt32 *count)
00562 {
00563   if (mFirstRange)
00564     *count = mFirstRange->Count();
00565   else // No range available, so there's no selected row.
00566     *count = 0;
00567   
00568   return NS_OK;
00569 }
00570 
00571 NS_IMETHODIMP nsTreeSelection::GetSelectEventsSuppressed(PRBool *aSelectEventsSuppressed)
00572 {
00573   *aSelectEventsSuppressed = mSuppressed;
00574   return NS_OK;
00575 }
00576 
00577 NS_IMETHODIMP nsTreeSelection::SetSelectEventsSuppressed(PRBool aSelectEventsSuppressed)
00578 {
00579   mSuppressed = aSelectEventsSuppressed;
00580   if (!mSuppressed)
00581     FireOnSelectHandler();
00582   return NS_OK;
00583 }
00584 
00585 NS_IMETHODIMP nsTreeSelection::GetCurrentIndex(PRInt32 *aCurrentIndex)
00586 {
00587   *aCurrentIndex = mCurrentIndex;
00588   return NS_OK;
00589 }
00590 
00591 NS_IMETHODIMP nsTreeSelection::SetCurrentIndex(PRInt32 aIndex)
00592 {
00593   if (mCurrentIndex == aIndex) {
00594     return NS_OK;
00595   }
00596   if (mCurrentIndex != -1)
00597     mTree->InvalidateRow(mCurrentIndex);
00598   
00599   mCurrentIndex = aIndex;
00600   
00601   if (aIndex != -1)
00602     mTree->InvalidateRow(aIndex);
00603 
00604   // Fire DOMMenuItemActive event for tree
00605   nsCOMPtr<nsIBoxObject> boxObject = do_QueryInterface(mTree);
00606   NS_ASSERTION(boxObject, "no box object!");
00607   if (!boxObject)
00608     return NS_ERROR_UNEXPECTED;
00609   nsCOMPtr<nsIDOMElement> treeElt;
00610   boxObject->GetElement(getter_AddRefs(treeElt));
00611 
00612   nsCOMPtr<nsIDOMNode> treeDOMNode(do_QueryInterface(treeElt));
00613   NS_ENSURE_TRUE(treeDOMNode, NS_ERROR_UNEXPECTED);
00614 
00615   nsPLDOMEvent *event = new nsPLDOMEvent(treeDOMNode,
00616                                          NS_LITERAL_STRING("DOMMenuItemActive"));
00617 
00618   nsresult rv;
00619   if (event) {
00620     rv = event->PostDOMEvent();
00621     if (NS_FAILED(rv)) {
00622       PL_DestroyEvent(event);
00623     }
00624   }
00625   else {
00626     rv = NS_ERROR_OUT_OF_MEMORY;
00627   }
00628   
00629   return rv;
00630 }
00631 
00632 #define ADD_NEW_RANGE(macro_range, macro_selection, macro_start, macro_end) \
00633   { \
00634     nsTreeRange* macro_new_range = new nsTreeRange(macro_selection, (macro_start), (macro_end)); \
00635     if (macro_range) \
00636       macro_range->Insert(macro_new_range); \
00637     else \
00638       macro_range = macro_new_range; \
00639   }
00640 
00641 NS_IMETHODIMP
00642 nsTreeSelection::AdjustSelection(PRInt32 aIndex, PRInt32 aCount)
00643 {
00644   NS_ASSERTION(aCount != 0, "adjusting by zero");
00645   if (!aCount) return NS_OK;
00646 
00647   // adjust mShiftSelectPivot, if necessary
00648   if ((mShiftSelectPivot != 1) && (aIndex <= mShiftSelectPivot)) {
00649     // if we are deleting and the delete includes the shift select pivot, reset it
00650     if (aCount < 0 && (mShiftSelectPivot <= (aIndex -aCount -1))) {
00651         mShiftSelectPivot = -1;
00652     }
00653     else {
00654         mShiftSelectPivot += aCount;
00655     }
00656   }
00657 
00658   // adjust mCurrentIndex, if necessary
00659   if ((mCurrentIndex != -1) && (aIndex <= mCurrentIndex)) {
00660     // if we are deleting and the delete includes the current index, reset it
00661     if (aCount < 0 && (mCurrentIndex <= (aIndex -aCount -1))) {
00662         mCurrentIndex = -1;
00663     }
00664     else {
00665         mCurrentIndex += aCount;
00666     }
00667   }
00668 
00669   // no selection, so nothing to do.
00670   if (!mFirstRange) return NS_OK;
00671 
00672   nsTreeRange* newRange = nsnull;
00673 
00674   PRBool selChanged = PR_FALSE;
00675   nsTreeRange* curr = mFirstRange;
00676   while (curr) {
00677     if (aCount > 0) {
00678       // inserting
00679       if (aIndex > curr->mMax) {
00680         // adjustment happens after the range, so no change
00681         ADD_NEW_RANGE(newRange, this, curr->mMin, curr->mMax);
00682       }
00683       else if (aIndex <= curr->mMin) {  
00684         // adjustment happens before the start of the range, so shift down
00685         ADD_NEW_RANGE(newRange, this, curr->mMin + aCount, curr->mMax + aCount);
00686         selChanged = PR_TRUE;
00687       }
00688       else {
00689         // adjustment happen inside the range.
00690         // break apart the range and create two ranges
00691         ADD_NEW_RANGE(newRange, this, curr->mMin, aIndex - 1);
00692         ADD_NEW_RANGE(newRange, this, aIndex + aCount, curr->mMax + aCount);
00693         selChanged = PR_TRUE;
00694       }
00695     }
00696     else {
00697       // deleting
00698       if (aIndex > curr->mMax) {
00699         // adjustment happens after the range, so no change
00700         ADD_NEW_RANGE(newRange, this, curr->mMin, curr->mMax);
00701       }
00702       else {
00703         // remember, aCount is negative
00704         selChanged = PR_TRUE;
00705         PRInt32 lastIndexOfAdjustment = aIndex - aCount - 1;
00706         if (aIndex <= curr->mMin) {
00707           if (lastIndexOfAdjustment < curr->mMin) {
00708             // adjustment happens before the start of the range, so shift up
00709             ADD_NEW_RANGE(newRange, this, curr->mMin + aCount, curr->mMax + aCount);
00710           }
00711           else if (lastIndexOfAdjustment >= curr->mMax) {
00712             // adjustment contains the range.  remove the range by not adding it to the newRange
00713           }
00714           else {
00715             // adjustment starts before the range, and ends in the middle of it, so trim the range
00716             ADD_NEW_RANGE(newRange, this, aIndex, curr->mMax + aCount)
00717           }
00718         }
00719         else if (lastIndexOfAdjustment >= curr->mMax) {
00720          // adjustment starts in the middle of the current range, and contains the end of the range, so trim the range
00721          ADD_NEW_RANGE(newRange, this, curr->mMin, aIndex - 1)
00722         }
00723         else {
00724           // range contains the adjustment, so shorten the range
00725           ADD_NEW_RANGE(newRange, this, curr->mMin, curr->mMax + aCount)
00726         }
00727       }
00728     }
00729     curr = curr->mNext;
00730   }
00731 
00732   delete mFirstRange;
00733   mFirstRange = newRange;
00734 
00735   // Fire the select event
00736   if (selChanged)
00737     FireOnSelectHandler();
00738 
00739   return NS_OK;
00740 }
00741 
00742 NS_IMETHODIMP
00743 nsTreeSelection::InvalidateSelection()
00744 {
00745   if (mFirstRange)
00746     mFirstRange->Invalidate();
00747   return NS_OK;
00748 }
00749 
00750 NS_IMETHODIMP
00751 nsTreeSelection::GetShiftSelectPivot(PRInt32* aIndex)
00752 {
00753   *aIndex = mShiftSelectPivot;
00754   return NS_OK;
00755 }
00756 
00757 
00758 nsresult
00759 nsTreeSelection::FireOnSelectHandler()
00760 {
00761   if (mSuppressed)
00762     return NS_OK;
00763 
00764   nsCOMPtr<nsIBoxObject> boxObject = do_QueryInterface(mTree);
00765   NS_ASSERTION(boxObject, "no box object!");
00766   if (!boxObject)
00767      return NS_ERROR_UNEXPECTED;
00768   nsCOMPtr<nsIDOMElement> elt;
00769   boxObject->GetElement(getter_AddRefs(elt));
00770 
00771   nsCOMPtr<nsIContent> content(do_QueryInterface(elt));
00772   nsCOMPtr<nsIDocument> document = content->GetDocument();
00773   
00774   // we might be firing on a delay, so it's possible in rare cases that
00775   // the document may have been destroyed by the time it fires
00776   if (!document)
00777     return NS_OK;
00778 
00779   nsIPresShell *shell = document->GetShellAt(0);
00780   if (shell) {
00781     // Retrieve the context in which our DOM event will fire.
00782     nsCOMPtr<nsPresContext> aPresContext = shell->GetPresContext();
00783 
00784     nsEventStatus status = nsEventStatus_eIgnore;
00785     nsEvent event(PR_TRUE, NS_FORM_SELECTED);
00786 
00787     content->HandleDOMEvent(aPresContext, &event, nsnull, NS_EVENT_FLAG_INIT,
00788                             &status);
00789   }
00790 
00791   return NS_OK;
00792 }
00793 
00794 void
00795 nsTreeSelection::SelectCallback(nsITimer *aTimer, void *aClosure)
00796 {
00797   nsTreeSelection* self = NS_STATIC_CAST(nsTreeSelection*, aClosure);
00798   if (self) {
00799     self->FireOnSelectHandler();
00800     aTimer->Cancel();
00801     self->mSelectTimer = nsnull;
00802   }
00803 }
00804 
00806 
00807 nsresult
00808 NS_NewTreeSelection(nsITreeBoxObject* aTree, nsITreeSelection** aResult)
00809 {
00810   *aResult = new nsTreeSelection(aTree);
00811   if (!*aResult)
00812     return NS_ERROR_OUT_OF_MEMORY;
00813   NS_ADDREF(*aResult);
00814   return NS_OK;
00815 }