michael@0: /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include "mozilla/AsyncEventDispatcher.h" michael@0: #include "nsCOMPtr.h" michael@0: #include "nsTreeSelection.h" michael@0: #include "nsIBoxObject.h" michael@0: #include "nsITreeBoxObject.h" michael@0: #include "nsITreeView.h" michael@0: #include "nsString.h" michael@0: #include "nsIDOMElement.h" michael@0: #include "nsDOMClassInfoID.h" michael@0: #include "nsIContent.h" michael@0: #include "nsNameSpaceManager.h" michael@0: #include "nsGkAtoms.h" michael@0: #include "nsAutoPtr.h" michael@0: #include "nsComponentManagerUtils.h" michael@0: michael@0: using namespace mozilla; michael@0: michael@0: // A helper class for managing our ranges of selection. michael@0: struct nsTreeRange michael@0: { michael@0: nsTreeSelection* mSelection; michael@0: michael@0: nsTreeRange* mPrev; michael@0: nsTreeRange* mNext; michael@0: michael@0: int32_t mMin; michael@0: int32_t mMax; michael@0: michael@0: nsTreeRange(nsTreeSelection* aSel, int32_t aSingleVal) michael@0: :mSelection(aSel), mPrev(nullptr), mNext(nullptr), mMin(aSingleVal), mMax(aSingleVal) {} michael@0: nsTreeRange(nsTreeSelection* aSel, int32_t aMin, int32_t aMax) michael@0: :mSelection(aSel), mPrev(nullptr), mNext(nullptr), mMin(aMin), mMax(aMax) {} michael@0: michael@0: ~nsTreeRange() { delete mNext; } michael@0: michael@0: void Connect(nsTreeRange* aPrev = nullptr, nsTreeRange* aNext = nullptr) { michael@0: if (aPrev) michael@0: aPrev->mNext = this; michael@0: else michael@0: mSelection->mFirstRange = this; michael@0: michael@0: if (aNext) michael@0: aNext->mPrev = this; michael@0: michael@0: mPrev = aPrev; michael@0: mNext = aNext; michael@0: } michael@0: michael@0: nsresult RemoveRange(int32_t aStart, int32_t aEnd) { michael@0: // This should so be a loop... sigh... michael@0: // We start past the range to remove, so no more to remove michael@0: if (aEnd < mMin) michael@0: return NS_OK; michael@0: // We are the last range to be affected michael@0: if (aEnd < mMax) { michael@0: if (aStart <= mMin) { michael@0: // Just chop the start of the range off michael@0: mMin = aEnd + 1; michael@0: } else { michael@0: // We need to split the range michael@0: nsTreeRange* range = new nsTreeRange(mSelection, aEnd + 1, mMax); michael@0: if (!range) michael@0: return NS_ERROR_OUT_OF_MEMORY; michael@0: michael@0: mMax = aStart - 1; michael@0: range->Connect(this, mNext); michael@0: } michael@0: return NS_OK; michael@0: } michael@0: nsTreeRange* next = mNext; michael@0: if (aStart <= mMin) { michael@0: // The remove includes us, remove ourselves from the list michael@0: if (mPrev) michael@0: mPrev->mNext = next; michael@0: else michael@0: mSelection->mFirstRange = next; michael@0: michael@0: if (next) michael@0: next->mPrev = mPrev; michael@0: mPrev = mNext = nullptr; michael@0: delete this; michael@0: } else if (aStart <= mMax) { michael@0: // Just chop the end of the range off michael@0: mMax = aStart - 1; michael@0: } michael@0: return next ? next->RemoveRange(aStart, aEnd) : NS_OK; michael@0: } michael@0: michael@0: nsresult Remove(int32_t aIndex) { michael@0: if (aIndex >= mMin && aIndex <= mMax) { michael@0: // We have found the range that contains us. michael@0: if (mMin == mMax) { michael@0: // Delete the whole range. michael@0: if (mPrev) michael@0: mPrev->mNext = mNext; michael@0: if (mNext) michael@0: mNext->mPrev = mPrev; michael@0: nsTreeRange* first = mSelection->mFirstRange; michael@0: if (first == this) michael@0: mSelection->mFirstRange = mNext; michael@0: mNext = mPrev = nullptr; michael@0: delete this; michael@0: } michael@0: else if (aIndex == mMin) michael@0: mMin++; michael@0: else if (aIndex == mMax) michael@0: mMax--; michael@0: else { michael@0: // We have to break this range. michael@0: nsTreeRange* newRange = new nsTreeRange(mSelection, aIndex + 1, mMax); michael@0: if (!newRange) michael@0: return NS_ERROR_OUT_OF_MEMORY; michael@0: michael@0: newRange->Connect(this, mNext); michael@0: mMax = aIndex - 1; michael@0: } michael@0: } michael@0: else if (mNext) michael@0: return mNext->Remove(aIndex); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult Add(int32_t aIndex) { michael@0: if (aIndex < mMin) { michael@0: // We have found a spot to insert. michael@0: if (aIndex + 1 == mMin) michael@0: mMin = aIndex; michael@0: else if (mPrev && mPrev->mMax+1 == aIndex) michael@0: mPrev->mMax = aIndex; michael@0: else { michael@0: // We have to create a new range. michael@0: nsTreeRange* newRange = new nsTreeRange(mSelection, aIndex); michael@0: if (!newRange) michael@0: return NS_ERROR_OUT_OF_MEMORY; michael@0: michael@0: newRange->Connect(mPrev, this); michael@0: } michael@0: } michael@0: else if (mNext) michael@0: mNext->Add(aIndex); michael@0: else { michael@0: // Insert on to the end. michael@0: if (mMax+1 == aIndex) michael@0: mMax = aIndex; michael@0: else { michael@0: // We have to create a new range. michael@0: nsTreeRange* newRange = new nsTreeRange(mSelection, aIndex); michael@0: if (!newRange) michael@0: return NS_ERROR_OUT_OF_MEMORY; michael@0: michael@0: newRange->Connect(this, nullptr); michael@0: } michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: bool Contains(int32_t aIndex) { michael@0: if (aIndex >= mMin && aIndex <= mMax) michael@0: return true; michael@0: michael@0: if (mNext) michael@0: return mNext->Contains(aIndex); michael@0: michael@0: return false; michael@0: } michael@0: michael@0: int32_t Count() { michael@0: int32_t total = mMax - mMin + 1; michael@0: if (mNext) michael@0: total += mNext->Count(); michael@0: return total; michael@0: } michael@0: michael@0: static void CollectRanges(nsTreeRange* aRange, nsTArray& aRanges) michael@0: { michael@0: nsTreeRange* cur = aRange; michael@0: while (cur) { michael@0: aRanges.AppendElement(cur->mMin); michael@0: aRanges.AppendElement(cur->mMax); michael@0: cur = cur->mNext; michael@0: } michael@0: } michael@0: michael@0: static void InvalidateRanges(nsITreeBoxObject* aTree, michael@0: nsTArray& aRanges) michael@0: { michael@0: if (aTree) { michael@0: nsCOMPtr tree = aTree; michael@0: for (uint32_t i = 0; i < aRanges.Length(); i += 2) { michael@0: aTree->InvalidateRange(aRanges[i], aRanges[i + 1]); michael@0: } michael@0: } michael@0: } michael@0: michael@0: void Invalidate() { michael@0: nsTArray ranges; michael@0: CollectRanges(this, ranges); michael@0: InvalidateRanges(mSelection->mTree, ranges); michael@0: michael@0: } michael@0: michael@0: void RemoveAllBut(int32_t aIndex) { michael@0: if (aIndex >= mMin && aIndex <= mMax) { michael@0: michael@0: // Invalidate everything in this list. michael@0: nsTArray ranges; michael@0: CollectRanges(mSelection->mFirstRange, ranges); michael@0: michael@0: mMin = aIndex; michael@0: mMax = aIndex; michael@0: michael@0: nsTreeRange* first = mSelection->mFirstRange; michael@0: if (mPrev) michael@0: mPrev->mNext = mNext; michael@0: if (mNext) michael@0: mNext->mPrev = mPrev; michael@0: mNext = mPrev = nullptr; michael@0: michael@0: if (first != this) { michael@0: delete mSelection->mFirstRange; michael@0: mSelection->mFirstRange = this; michael@0: } michael@0: InvalidateRanges(mSelection->mTree, ranges); michael@0: } michael@0: else if (mNext) michael@0: mNext->RemoveAllBut(aIndex); michael@0: } michael@0: michael@0: void Insert(nsTreeRange* aRange) { michael@0: if (mMin >= aRange->mMax) michael@0: aRange->Connect(mPrev, this); michael@0: else if (mNext) michael@0: mNext->Insert(aRange); michael@0: else michael@0: aRange->Connect(this, nullptr); michael@0: } michael@0: }; michael@0: michael@0: nsTreeSelection::nsTreeSelection(nsITreeBoxObject* aTree) michael@0: : mTree(aTree), michael@0: mSuppressed(false), michael@0: mCurrentIndex(-1), michael@0: mShiftSelectPivot(-1), michael@0: mFirstRange(nullptr) michael@0: { michael@0: } michael@0: michael@0: nsTreeSelection::~nsTreeSelection() michael@0: { michael@0: delete mFirstRange; michael@0: if (mSelectTimer) michael@0: mSelectTimer->Cancel(); michael@0: } michael@0: michael@0: NS_IMPL_CYCLE_COLLECTION(nsTreeSelection, mTree, mCurrentColumn) michael@0: michael@0: NS_IMPL_CYCLE_COLLECTING_ADDREF(nsTreeSelection) michael@0: NS_IMPL_CYCLE_COLLECTING_RELEASE(nsTreeSelection) michael@0: michael@0: DOMCI_DATA(TreeSelection, nsTreeSelection) michael@0: michael@0: // QueryInterface implementation for nsBoxObject michael@0: NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsTreeSelection) michael@0: NS_INTERFACE_MAP_ENTRY(nsITreeSelection) michael@0: NS_INTERFACE_MAP_ENTRY(nsINativeTreeSelection) michael@0: NS_INTERFACE_MAP_ENTRY(nsISupports) michael@0: NS_DOM_INTERFACE_MAP_ENTRY_CLASSINFO(TreeSelection) michael@0: NS_INTERFACE_MAP_END michael@0: michael@0: NS_IMETHODIMP nsTreeSelection::GetTree(nsITreeBoxObject * *aTree) michael@0: { michael@0: NS_IF_ADDREF(*aTree = mTree); michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP nsTreeSelection::SetTree(nsITreeBoxObject * aTree) michael@0: { michael@0: if (mSelectTimer) { michael@0: mSelectTimer->Cancel(); michael@0: mSelectTimer = nullptr; michael@0: } michael@0: michael@0: // Make sure aTree really implements nsITreeBoxObject and nsIBoxObject! michael@0: nsCOMPtr bo = do_QueryInterface(aTree); michael@0: mTree = do_QueryInterface(bo); michael@0: NS_ENSURE_STATE(mTree == aTree); michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP nsTreeSelection::GetSingle(bool* aSingle) michael@0: { michael@0: if (!mTree) michael@0: return NS_ERROR_NULL_POINTER; michael@0: michael@0: nsCOMPtr boxObject = do_QueryInterface(mTree); michael@0: michael@0: nsCOMPtr element; michael@0: boxObject->GetElement(getter_AddRefs(element)); michael@0: michael@0: nsCOMPtr content = do_QueryInterface(element); michael@0: michael@0: static nsIContent::AttrValuesArray strings[] = michael@0: {&nsGkAtoms::single, &nsGkAtoms::cell, &nsGkAtoms::text, nullptr}; michael@0: michael@0: *aSingle = content->FindAttrValueIn(kNameSpaceID_None, michael@0: nsGkAtoms::seltype, michael@0: strings, eCaseMatters) >= 0; michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP nsTreeSelection::IsSelected(int32_t aIndex, bool* aResult) michael@0: { michael@0: if (mFirstRange) michael@0: *aResult = mFirstRange->Contains(aIndex); michael@0: else michael@0: *aResult = false; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP nsTreeSelection::TimedSelect(int32_t aIndex, int32_t aMsec) michael@0: { michael@0: bool suppressSelect = mSuppressed; michael@0: michael@0: if (aMsec != -1) michael@0: mSuppressed = true; michael@0: michael@0: nsresult rv = Select(aIndex); michael@0: if (NS_FAILED(rv)) michael@0: return rv; michael@0: michael@0: if (aMsec != -1) { michael@0: mSuppressed = suppressSelect; michael@0: if (!mSuppressed) { michael@0: if (mSelectTimer) michael@0: mSelectTimer->Cancel(); michael@0: michael@0: mSelectTimer = do_CreateInstance("@mozilla.org/timer;1"); michael@0: mSelectTimer->InitWithFuncCallback(SelectCallback, this, aMsec, michael@0: nsITimer::TYPE_ONE_SHOT); michael@0: } michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP nsTreeSelection::Select(int32_t aIndex) michael@0: { michael@0: mShiftSelectPivot = -1; michael@0: michael@0: nsresult rv = SetCurrentIndex(aIndex); michael@0: if (NS_FAILED(rv)) michael@0: return rv; michael@0: michael@0: if (mFirstRange) { michael@0: bool alreadySelected = mFirstRange->Contains(aIndex); michael@0: michael@0: if (alreadySelected) { michael@0: int32_t count = mFirstRange->Count(); michael@0: if (count > 1) { michael@0: // We need to deselect everything but our item. michael@0: mFirstRange->RemoveAllBut(aIndex); michael@0: FireOnSelectHandler(); michael@0: } michael@0: return NS_OK; michael@0: } michael@0: else { michael@0: // Clear out our selection. michael@0: mFirstRange->Invalidate(); michael@0: delete mFirstRange; michael@0: } michael@0: } michael@0: michael@0: // Create our new selection. michael@0: mFirstRange = new nsTreeRange(this, aIndex); michael@0: if (!mFirstRange) michael@0: return NS_ERROR_OUT_OF_MEMORY; michael@0: michael@0: mFirstRange->Invalidate(); michael@0: michael@0: // Fire the select event michael@0: FireOnSelectHandler(); michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP nsTreeSelection::ToggleSelect(int32_t aIndex) michael@0: { michael@0: // There are six cases that can occur on a ToggleSelect with our michael@0: // range code. michael@0: // (1) A new range should be made for a selection. michael@0: // (2) A single range is removed from the selection. michael@0: // (3) The item is added to an existing range. michael@0: // (4) The item is removed from an existing range. michael@0: // (5) The addition of the item causes two ranges to be merged. michael@0: // (6) The removal of the item causes two ranges to be split. michael@0: mShiftSelectPivot = -1; michael@0: nsresult rv = SetCurrentIndex(aIndex); michael@0: if (NS_FAILED(rv)) michael@0: return rv; michael@0: michael@0: if (!mFirstRange) michael@0: Select(aIndex); michael@0: else { michael@0: if (!mFirstRange->Contains(aIndex)) { michael@0: bool single; michael@0: rv = GetSingle(&single); michael@0: if (NS_SUCCEEDED(rv) && !single) michael@0: rv = mFirstRange->Add(aIndex); michael@0: } michael@0: else michael@0: rv = mFirstRange->Remove(aIndex); michael@0: if (NS_SUCCEEDED(rv)) { michael@0: if (mTree) michael@0: mTree->InvalidateRow(aIndex); michael@0: michael@0: FireOnSelectHandler(); michael@0: } michael@0: } michael@0: michael@0: return rv; michael@0: } michael@0: michael@0: NS_IMETHODIMP nsTreeSelection::RangedSelect(int32_t aStartIndex, int32_t aEndIndex, bool aAugment) michael@0: { michael@0: bool single; michael@0: nsresult rv = GetSingle(&single); michael@0: if (NS_FAILED(rv)) michael@0: return rv; michael@0: michael@0: if ((mFirstRange || (aStartIndex != aEndIndex)) && single) michael@0: return NS_OK; michael@0: michael@0: if (!aAugment) { michael@0: // Clear our selection. michael@0: if (mFirstRange) { michael@0: mFirstRange->Invalidate(); michael@0: delete mFirstRange; michael@0: mFirstRange = nullptr; michael@0: } michael@0: } michael@0: michael@0: if (aStartIndex == -1) { michael@0: if (mShiftSelectPivot != -1) michael@0: aStartIndex = mShiftSelectPivot; michael@0: else if (mCurrentIndex != -1) michael@0: aStartIndex = mCurrentIndex; michael@0: else michael@0: aStartIndex = aEndIndex; michael@0: } michael@0: michael@0: mShiftSelectPivot = aStartIndex; michael@0: rv = SetCurrentIndex(aEndIndex); michael@0: if (NS_FAILED(rv)) michael@0: return rv; michael@0: michael@0: int32_t start = aStartIndex < aEndIndex ? aStartIndex : aEndIndex; michael@0: int32_t end = aStartIndex < aEndIndex ? aEndIndex : aStartIndex; michael@0: michael@0: if (aAugment && mFirstRange) { michael@0: // We need to remove all the items within our selected range from the selection, michael@0: // and then we insert our new range into the list. michael@0: nsresult rv = mFirstRange->RemoveRange(start, end); michael@0: if (NS_FAILED(rv)) michael@0: return rv; michael@0: } michael@0: michael@0: nsTreeRange* range = new nsTreeRange(this, start, end); michael@0: if (!range) michael@0: return NS_ERROR_OUT_OF_MEMORY; michael@0: michael@0: range->Invalidate(); michael@0: michael@0: if (aAugment && mFirstRange) michael@0: mFirstRange->Insert(range); michael@0: else michael@0: mFirstRange = range; michael@0: michael@0: FireOnSelectHandler(); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP nsTreeSelection::ClearRange(int32_t aStartIndex, int32_t aEndIndex) michael@0: { michael@0: nsresult rv = SetCurrentIndex(aEndIndex); michael@0: if (NS_FAILED(rv)) michael@0: return rv; michael@0: michael@0: if (mFirstRange) { michael@0: int32_t start = aStartIndex < aEndIndex ? aStartIndex : aEndIndex; michael@0: int32_t end = aStartIndex < aEndIndex ? aEndIndex : aStartIndex; michael@0: michael@0: mFirstRange->RemoveRange(start, end); michael@0: michael@0: if (mTree) michael@0: mTree->InvalidateRange(start, end); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP nsTreeSelection::ClearSelection() michael@0: { michael@0: if (mFirstRange) { michael@0: mFirstRange->Invalidate(); michael@0: delete mFirstRange; michael@0: mFirstRange = nullptr; michael@0: } michael@0: mShiftSelectPivot = -1; michael@0: michael@0: FireOnSelectHandler(); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP nsTreeSelection::InvertSelection() michael@0: { michael@0: return NS_ERROR_NOT_IMPLEMENTED; michael@0: } michael@0: michael@0: NS_IMETHODIMP nsTreeSelection::SelectAll() michael@0: { michael@0: if (!mTree) michael@0: return NS_OK; michael@0: michael@0: nsCOMPtr view; michael@0: mTree->GetView(getter_AddRefs(view)); michael@0: if (!view) michael@0: return NS_OK; michael@0: michael@0: int32_t rowCount; michael@0: view->GetRowCount(&rowCount); michael@0: bool single; michael@0: nsresult rv = GetSingle(&single); michael@0: if (NS_FAILED(rv)) michael@0: return rv; michael@0: michael@0: if (rowCount == 0 || (rowCount > 1 && single)) michael@0: return NS_OK; michael@0: michael@0: mShiftSelectPivot = -1; michael@0: michael@0: // Invalidate not necessary when clearing selection, since michael@0: // we're going to invalidate the world on the SelectAll. michael@0: delete mFirstRange; michael@0: michael@0: mFirstRange = new nsTreeRange(this, 0, rowCount-1); michael@0: mFirstRange->Invalidate(); michael@0: michael@0: FireOnSelectHandler(); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP nsTreeSelection::GetRangeCount(int32_t* aResult) michael@0: { michael@0: int32_t count = 0; michael@0: nsTreeRange* curr = mFirstRange; michael@0: while (curr) { michael@0: count++; michael@0: curr = curr->mNext; michael@0: } michael@0: michael@0: *aResult = count; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP nsTreeSelection::GetRangeAt(int32_t aIndex, int32_t* aMin, int32_t* aMax) michael@0: { michael@0: *aMin = *aMax = -1; michael@0: int32_t i = -1; michael@0: nsTreeRange* curr = mFirstRange; michael@0: while (curr) { michael@0: i++; michael@0: if (i == aIndex) { michael@0: *aMin = curr->mMin; michael@0: *aMax = curr->mMax; michael@0: break; michael@0: } michael@0: curr = curr->mNext; michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP nsTreeSelection::GetCount(int32_t *count) michael@0: { michael@0: if (mFirstRange) michael@0: *count = mFirstRange->Count(); michael@0: else // No range available, so there's no selected row. michael@0: *count = 0; michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP nsTreeSelection::GetSelectEventsSuppressed(bool *aSelectEventsSuppressed) michael@0: { michael@0: *aSelectEventsSuppressed = mSuppressed; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP nsTreeSelection::SetSelectEventsSuppressed(bool aSelectEventsSuppressed) michael@0: { michael@0: mSuppressed = aSelectEventsSuppressed; michael@0: if (!mSuppressed) michael@0: FireOnSelectHandler(); michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP nsTreeSelection::GetCurrentIndex(int32_t *aCurrentIndex) michael@0: { michael@0: *aCurrentIndex = mCurrentIndex; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP nsTreeSelection::SetCurrentIndex(int32_t aIndex) michael@0: { michael@0: if (!mTree) { michael@0: return NS_ERROR_UNEXPECTED; michael@0: } michael@0: if (mCurrentIndex == aIndex) { michael@0: return NS_OK; michael@0: } michael@0: if (mCurrentIndex != -1 && mTree) michael@0: mTree->InvalidateRow(mCurrentIndex); michael@0: michael@0: mCurrentIndex = aIndex; michael@0: if (!mTree) michael@0: return NS_OK; michael@0: michael@0: if (aIndex != -1) michael@0: mTree->InvalidateRow(aIndex); michael@0: michael@0: // Fire DOMMenuItemActive or DOMMenuItemInactive event for tree. michael@0: nsCOMPtr boxObject = do_QueryInterface(mTree); michael@0: NS_ASSERTION(boxObject, "no box object!"); michael@0: if (!boxObject) michael@0: return NS_ERROR_UNEXPECTED; michael@0: nsCOMPtr treeElt; michael@0: boxObject->GetElement(getter_AddRefs(treeElt)); michael@0: michael@0: nsCOMPtr treeDOMNode(do_QueryInterface(treeElt)); michael@0: NS_ENSURE_STATE(treeDOMNode); michael@0: michael@0: NS_NAMED_LITERAL_STRING(DOMMenuItemActive, "DOMMenuItemActive"); michael@0: NS_NAMED_LITERAL_STRING(DOMMenuItemInactive, "DOMMenuItemInactive"); michael@0: michael@0: nsRefPtr asyncDispatcher = michael@0: new AsyncEventDispatcher(treeDOMNode, michael@0: (aIndex != -1 ? DOMMenuItemActive : michael@0: DOMMenuItemInactive), michael@0: true, false); michael@0: return asyncDispatcher->PostDOMEvent(); michael@0: } michael@0: michael@0: NS_IMETHODIMP nsTreeSelection::GetCurrentColumn(nsITreeColumn** aCurrentColumn) michael@0: { michael@0: NS_IF_ADDREF(*aCurrentColumn = mCurrentColumn); michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP nsTreeSelection::SetCurrentColumn(nsITreeColumn* aCurrentColumn) michael@0: { michael@0: if (!mTree) { michael@0: return NS_ERROR_UNEXPECTED; michael@0: } michael@0: if (mCurrentColumn == aCurrentColumn) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: if (mCurrentColumn) { michael@0: if (mFirstRange) michael@0: mTree->InvalidateCell(mFirstRange->mMin, mCurrentColumn); michael@0: if (mCurrentIndex != -1) michael@0: mTree->InvalidateCell(mCurrentIndex, mCurrentColumn); michael@0: } michael@0: michael@0: mCurrentColumn = aCurrentColumn; michael@0: michael@0: if (mCurrentColumn) { michael@0: if (mFirstRange) michael@0: mTree->InvalidateCell(mFirstRange->mMin, mCurrentColumn); michael@0: if (mCurrentIndex != -1) michael@0: mTree->InvalidateCell(mCurrentIndex, mCurrentColumn); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: #define ADD_NEW_RANGE(macro_range, macro_selection, macro_start, macro_end) \ michael@0: { \ michael@0: int32_t start = macro_start; \ michael@0: int32_t end = macro_end; \ michael@0: if (start > end) { \ michael@0: end = start; \ michael@0: } \ michael@0: nsTreeRange* macro_new_range = new nsTreeRange(macro_selection, start, end); \ michael@0: if (macro_range) \ michael@0: macro_range->Insert(macro_new_range); \ michael@0: else \ michael@0: macro_range = macro_new_range; \ michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsTreeSelection::AdjustSelection(int32_t aIndex, int32_t aCount) michael@0: { michael@0: NS_ASSERTION(aCount != 0, "adjusting by zero"); michael@0: if (!aCount) return NS_OK; michael@0: michael@0: // adjust mShiftSelectPivot, if necessary michael@0: if ((mShiftSelectPivot != 1) && (aIndex <= mShiftSelectPivot)) { michael@0: // if we are deleting and the delete includes the shift select pivot, reset it michael@0: if (aCount < 0 && (mShiftSelectPivot <= (aIndex -aCount -1))) { michael@0: mShiftSelectPivot = -1; michael@0: } michael@0: else { michael@0: mShiftSelectPivot += aCount; michael@0: } michael@0: } michael@0: michael@0: // adjust mCurrentIndex, if necessary michael@0: if ((mCurrentIndex != -1) && (aIndex <= mCurrentIndex)) { michael@0: // if we are deleting and the delete includes the current index, reset it michael@0: if (aCount < 0 && (mCurrentIndex <= (aIndex -aCount -1))) { michael@0: mCurrentIndex = -1; michael@0: } michael@0: else { michael@0: mCurrentIndex += aCount; michael@0: } michael@0: } michael@0: michael@0: // no selection, so nothing to do. michael@0: if (!mFirstRange) return NS_OK; michael@0: michael@0: bool selChanged = false; michael@0: nsTreeRange* oldFirstRange = mFirstRange; michael@0: nsTreeRange* curr = mFirstRange; michael@0: mFirstRange = nullptr; michael@0: while (curr) { michael@0: if (aCount > 0) { michael@0: // inserting michael@0: if (aIndex > curr->mMax) { michael@0: // adjustment happens after the range, so no change michael@0: ADD_NEW_RANGE(mFirstRange, this, curr->mMin, curr->mMax); michael@0: } michael@0: else if (aIndex <= curr->mMin) { michael@0: // adjustment happens before the start of the range, so shift down michael@0: ADD_NEW_RANGE(mFirstRange, this, curr->mMin + aCount, curr->mMax + aCount); michael@0: selChanged = true; michael@0: } michael@0: else { michael@0: // adjustment happen inside the range. michael@0: // break apart the range and create two ranges michael@0: ADD_NEW_RANGE(mFirstRange, this, curr->mMin, aIndex - 1); michael@0: ADD_NEW_RANGE(mFirstRange, this, aIndex + aCount, curr->mMax + aCount); michael@0: selChanged = true; michael@0: } michael@0: } michael@0: else { michael@0: // deleting michael@0: if (aIndex > curr->mMax) { michael@0: // adjustment happens after the range, so no change michael@0: ADD_NEW_RANGE(mFirstRange, this, curr->mMin, curr->mMax); michael@0: } michael@0: else { michael@0: // remember, aCount is negative michael@0: selChanged = true; michael@0: int32_t lastIndexOfAdjustment = aIndex - aCount - 1; michael@0: if (aIndex <= curr->mMin) { michael@0: if (lastIndexOfAdjustment < curr->mMin) { michael@0: // adjustment happens before the start of the range, so shift up michael@0: ADD_NEW_RANGE(mFirstRange, this, curr->mMin + aCount, curr->mMax + aCount); michael@0: } michael@0: else if (lastIndexOfAdjustment >= curr->mMax) { michael@0: // adjustment contains the range. remove the range by not adding it to the newRange michael@0: } michael@0: else { michael@0: // adjustment starts before the range, and ends in the middle of it, so trim the range michael@0: ADD_NEW_RANGE(mFirstRange, this, aIndex, curr->mMax + aCount) michael@0: } michael@0: } michael@0: else if (lastIndexOfAdjustment >= curr->mMax) { michael@0: // adjustment starts in the middle of the current range, and contains the end of the range, so trim the range michael@0: ADD_NEW_RANGE(mFirstRange, this, curr->mMin, aIndex - 1) michael@0: } michael@0: else { michael@0: // range contains the adjustment, so shorten the range michael@0: ADD_NEW_RANGE(mFirstRange, this, curr->mMin, curr->mMax + aCount) michael@0: } michael@0: } michael@0: } michael@0: curr = curr->mNext; michael@0: } michael@0: michael@0: delete oldFirstRange; michael@0: michael@0: // Fire the select event michael@0: if (selChanged) michael@0: FireOnSelectHandler(); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsTreeSelection::InvalidateSelection() michael@0: { michael@0: if (mFirstRange) michael@0: mFirstRange->Invalidate(); michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsTreeSelection::GetShiftSelectPivot(int32_t* aIndex) michael@0: { michael@0: *aIndex = mShiftSelectPivot; michael@0: return NS_OK; michael@0: } michael@0: michael@0: michael@0: nsresult michael@0: nsTreeSelection::FireOnSelectHandler() michael@0: { michael@0: if (mSuppressed || !mTree) michael@0: return NS_OK; michael@0: michael@0: nsCOMPtr boxObject = do_QueryInterface(mTree); michael@0: NS_ASSERTION(boxObject, "no box object!"); michael@0: if (!boxObject) michael@0: return NS_ERROR_UNEXPECTED; michael@0: nsCOMPtr elt; michael@0: boxObject->GetElement(getter_AddRefs(elt)); michael@0: NS_ENSURE_STATE(elt); michael@0: michael@0: nsCOMPtr node(do_QueryInterface(elt)); michael@0: NS_ENSURE_STATE(node); michael@0: michael@0: nsRefPtr asyncDispatcher = michael@0: new AsyncEventDispatcher(node, NS_LITERAL_STRING("select"), true, false); michael@0: asyncDispatcher->RunDOMEventWhenSafe(); michael@0: return NS_OK; michael@0: } michael@0: michael@0: void michael@0: nsTreeSelection::SelectCallback(nsITimer *aTimer, void *aClosure) michael@0: { michael@0: nsRefPtr self = static_cast(aClosure); michael@0: if (self) { michael@0: self->FireOnSelectHandler(); michael@0: aTimer->Cancel(); michael@0: self->mSelectTimer = nullptr; michael@0: } michael@0: } michael@0: michael@0: /////////////////////////////////////////////////////////////////////////////////// michael@0: michael@0: nsresult michael@0: NS_NewTreeSelection(nsITreeBoxObject* aTree, nsITreeSelection** aResult) michael@0: { michael@0: *aResult = new nsTreeSelection(aTree); michael@0: if (!*aResult) michael@0: return NS_ERROR_OUT_OF_MEMORY; michael@0: NS_ADDREF(*aResult); michael@0: return NS_OK; michael@0: }