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 "DeleteNodeTxn.h" michael@0: #include "DeleteRangeTxn.h" michael@0: #include "DeleteTextTxn.h" michael@0: #include "mozilla/Assertions.h" michael@0: #include "mozilla/dom/Selection.h" michael@0: #include "mozilla/mozalloc.h" michael@0: #include "nsCOMPtr.h" michael@0: #include "nsDebug.h" michael@0: #include "nsEditor.h" michael@0: #include "nsError.h" michael@0: #include "nsIContent.h" michael@0: #include "nsIContentIterator.h" michael@0: #include "nsIDOMCharacterData.h" michael@0: #include "nsINode.h" michael@0: #include "nsAString.h" michael@0: michael@0: class nsIDOMRange; michael@0: michael@0: using namespace mozilla; michael@0: using namespace mozilla::dom; michael@0: michael@0: // note that aEditor is not refcounted michael@0: DeleteRangeTxn::DeleteRangeTxn() michael@0: : EditAggregateTxn(), michael@0: mRange(), michael@0: mEditor(nullptr), michael@0: mRangeUpdater(nullptr) michael@0: { michael@0: } michael@0: michael@0: NS_IMPL_CYCLE_COLLECTION_INHERITED(DeleteRangeTxn, EditAggregateTxn, michael@0: mRange) michael@0: michael@0: NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(DeleteRangeTxn) michael@0: NS_INTERFACE_MAP_END_INHERITING(EditAggregateTxn) michael@0: michael@0: nsresult michael@0: DeleteRangeTxn::Init(nsEditor* aEditor, michael@0: nsRange* aRange, michael@0: nsRangeUpdater* aRangeUpdater) michael@0: { michael@0: MOZ_ASSERT(aEditor && aRange); michael@0: michael@0: mEditor = aEditor; michael@0: mRange = aRange->CloneRange(); michael@0: mRangeUpdater = aRangeUpdater; michael@0: michael@0: NS_ENSURE_TRUE(mEditor->IsModifiableNode(mRange->GetStartParent()), michael@0: NS_ERROR_FAILURE); michael@0: NS_ENSURE_TRUE(mEditor->IsModifiableNode(mRange->GetEndParent()), michael@0: NS_ERROR_FAILURE); michael@0: NS_ENSURE_TRUE(mEditor->IsModifiableNode(mRange->GetCommonAncestor()), michael@0: NS_ERROR_FAILURE); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: DeleteRangeTxn::DoTransaction() michael@0: { michael@0: MOZ_ASSERT(mRange && mEditor); michael@0: nsresult res; michael@0: michael@0: // build the child transactions michael@0: nsCOMPtr startParent = mRange->GetStartParent(); michael@0: int32_t startOffset = mRange->StartOffset(); michael@0: nsCOMPtr endParent = mRange->GetEndParent(); michael@0: int32_t endOffset = mRange->EndOffset(); michael@0: MOZ_ASSERT(startParent && endParent); michael@0: michael@0: if (startParent == endParent) { michael@0: // the selection begins and ends in the same node michael@0: res = CreateTxnsToDeleteBetween(startParent, startOffset, endOffset); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: } else { michael@0: // the selection ends in a different node from where it started. delete michael@0: // the relevant content in the start node michael@0: res = CreateTxnsToDeleteContent(startParent, startOffset, nsIEditor::eNext); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: // delete the intervening nodes michael@0: res = CreateTxnsToDeleteNodesBetween(); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: // delete the relevant content in the end node michael@0: res = CreateTxnsToDeleteContent(endParent, endOffset, nsIEditor::ePrevious); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: } michael@0: michael@0: // if we've successfully built this aggregate transaction, then do it. michael@0: res = EditAggregateTxn::DoTransaction(); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: michael@0: // only set selection to deletion point if editor gives permission michael@0: bool bAdjustSelection; michael@0: mEditor->ShouldTxnSetSelection(&bAdjustSelection); michael@0: if (bAdjustSelection) { michael@0: nsRefPtr selection = mEditor->GetSelection(); michael@0: NS_ENSURE_TRUE(selection, NS_ERROR_NULL_POINTER); michael@0: res = selection->Collapse(startParent, startOffset); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: } michael@0: // else do nothing - dom range gravity will adjust selection michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: DeleteRangeTxn::UndoTransaction() michael@0: { michael@0: MOZ_ASSERT(mRange && mEditor); michael@0: michael@0: return EditAggregateTxn::UndoTransaction(); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: DeleteRangeTxn::RedoTransaction() michael@0: { michael@0: MOZ_ASSERT(mRange && mEditor); michael@0: michael@0: return EditAggregateTxn::RedoTransaction(); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: DeleteRangeTxn::GetTxnDescription(nsAString& aString) michael@0: { michael@0: aString.AssignLiteral("DeleteRangeTxn"); michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: DeleteRangeTxn::CreateTxnsToDeleteBetween(nsINode* aNode, michael@0: int32_t aStartOffset, michael@0: int32_t aEndOffset) michael@0: { michael@0: // see what kind of node we have michael@0: if (aNode->IsNodeOfType(nsINode::eDATA_NODE)) { michael@0: // if the node is a chardata node, then delete chardata content michael@0: nsRefPtr txn = new DeleteTextTxn(); michael@0: michael@0: int32_t numToDel; michael@0: if (aStartOffset == aEndOffset) { michael@0: numToDel = 1; michael@0: } else { michael@0: numToDel = aEndOffset - aStartOffset; michael@0: } michael@0: michael@0: nsCOMPtr charDataNode = do_QueryInterface(aNode); michael@0: nsresult res = txn->Init(mEditor, charDataNode, aStartOffset, numToDel, michael@0: mRangeUpdater); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: michael@0: AppendChild(txn); michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsCOMPtr child = aNode->GetChildAt(aStartOffset); michael@0: NS_ENSURE_STATE(child); michael@0: michael@0: nsresult res = NS_OK; michael@0: for (int32_t i = aStartOffset; i < aEndOffset; ++i) { michael@0: nsRefPtr txn = new DeleteNodeTxn(); michael@0: res = txn->Init(mEditor, child, mRangeUpdater); michael@0: if (NS_SUCCEEDED(res)) { michael@0: AppendChild(txn); michael@0: } michael@0: michael@0: child = child->GetNextSibling(); michael@0: } michael@0: michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: DeleteRangeTxn::CreateTxnsToDeleteContent(nsINode* aNode, michael@0: int32_t aOffset, michael@0: nsIEditor::EDirection aAction) michael@0: { michael@0: // see what kind of node we have michael@0: if (aNode->IsNodeOfType(nsINode::eDATA_NODE)) { michael@0: // if the node is a chardata node, then delete chardata content michael@0: uint32_t start, numToDelete; michael@0: if (nsIEditor::eNext == aAction) { michael@0: start = aOffset; michael@0: numToDelete = aNode->Length() - aOffset; michael@0: } else { michael@0: start = 0; michael@0: numToDelete = aOffset; michael@0: } michael@0: michael@0: if (numToDelete) { michael@0: nsRefPtr txn = new DeleteTextTxn(); michael@0: michael@0: nsCOMPtr charDataNode = do_QueryInterface(aNode); michael@0: nsresult res = txn->Init(mEditor, charDataNode, start, numToDelete, michael@0: mRangeUpdater); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: michael@0: AppendChild(txn); michael@0: } michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: DeleteRangeTxn::CreateTxnsToDeleteNodesBetween() michael@0: { michael@0: nsCOMPtr iter = NS_NewContentSubtreeIterator(); michael@0: michael@0: nsresult res = iter->Init(mRange); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: michael@0: while (!iter->IsDone()) { michael@0: nsCOMPtr node = iter->GetCurrentNode(); michael@0: NS_ENSURE_TRUE(node, NS_ERROR_NULL_POINTER); michael@0: michael@0: nsRefPtr txn = new DeleteNodeTxn(); michael@0: michael@0: res = txn->Init(mEditor, node, mRangeUpdater); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: AppendChild(txn); michael@0: michael@0: iter->Next(); michael@0: } michael@0: return NS_OK; michael@0: }