/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- * * The contents of this file are subject to the Netscape Public * License Version 1.1 (the "License"); you may not use this file * except in compliance with the License. You may obtain a copy of * the License at http://www.mozilla.org/NPL/ * * Software distributed under the License is distributed on an "AS * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or * implied. See the License for the specific language governing * rights and limitations under the License. * * The Original Code is mozilla.org code. * * The Initial Developer of the Original Code is Netscape * Communications Corporation. Portions created by Netscape are * Copyright (C) 1998 Netscape Communications Corporation. All * Rights Reserved. * * Contributor(s): * Pierre Phaneuf */ #include "nsLayoutCID.h" #include "nsIAtom.h" #include "nsIContent.h" #include "nsIContentIterator.h" #include "nsIDOMNodeList.h" #include "nsIDOMRange.h" #include "nsISelection.h" #include "nsIPlaintextEditor.h" #include "nsTextServicesDocument.h" #include "nsIDOMElement.h" #include "nsIDOMHTMLElement.h" #include "nsIDOMHTMLDocument.h" #define LOCK_DOC(doc) #define UNLOCK_DOC(doc) static NS_DEFINE_CID(kCContentIteratorCID, NS_CONTENTITERATOR_CID); static NS_DEFINE_CID(kCDOMRangeCID, NS_RANGE_CID); static NS_DEFINE_IID(kISupportsIID, NS_ISUPPORTS_IID); static NS_DEFINE_IID(kITextServicesDocumentIID, NS_ITEXTSERVICESDOCUMENT_IID); class OffsetEntry { public: OffsetEntry(nsIDOMNode *aNode, PRInt32 aOffset, PRInt32 aLength) : mNode(aNode), mNodeOffset(0), mStrOffset(aOffset), mLength(aLength), mIsInsertedText(PR_FALSE), mIsValid(PR_TRUE) { if (mStrOffset < 1) mStrOffset = 0; if (mLength < 1) mLength = 0; } virtual ~OffsetEntry() { mNode = 0; mNodeOffset = 0; mStrOffset = 0; mLength = 0; mIsValid = PR_FALSE; } nsIDOMNode *mNode; PRInt32 mNodeOffset; PRInt32 mStrOffset; PRInt32 mLength; PRBool mIsInsertedText; PRBool mIsValid; }; // XXX: Should change these pointers to // nsCOMPtr pointers! nsIAtom *nsTextServicesDocument::sAAtom; nsIAtom *nsTextServicesDocument::sAddressAtom; nsIAtom *nsTextServicesDocument::sBigAtom; nsIAtom *nsTextServicesDocument::sBlinkAtom; nsIAtom *nsTextServicesDocument::sBAtom; nsIAtom *nsTextServicesDocument::sCiteAtom; nsIAtom *nsTextServicesDocument::sCodeAtom; nsIAtom *nsTextServicesDocument::sDfnAtom; nsIAtom *nsTextServicesDocument::sEmAtom; nsIAtom *nsTextServicesDocument::sFontAtom; nsIAtom *nsTextServicesDocument::sIAtom; nsIAtom *nsTextServicesDocument::sKbdAtom; nsIAtom *nsTextServicesDocument::sKeygenAtom; nsIAtom *nsTextServicesDocument::sNobrAtom; nsIAtom *nsTextServicesDocument::sSAtom; nsIAtom *nsTextServicesDocument::sSampAtom; nsIAtom *nsTextServicesDocument::sSmallAtom; nsIAtom *nsTextServicesDocument::sSpacerAtom; nsIAtom *nsTextServicesDocument::sSpanAtom; nsIAtom *nsTextServicesDocument::sStrikeAtom; nsIAtom *nsTextServicesDocument::sStrongAtom; nsIAtom *nsTextServicesDocument::sSubAtom; nsIAtom *nsTextServicesDocument::sSupAtom; nsIAtom *nsTextServicesDocument::sTtAtom; nsIAtom *nsTextServicesDocument::sUAtom; nsIAtom *nsTextServicesDocument::sVarAtom; nsIAtom *nsTextServicesDocument::sWbrAtom; PRInt32 nsTextServicesDocument::sInstanceCount; nsTextServicesDocument::nsTextServicesDocument() { mRefCnt = 0; mSelStartIndex = -1; mSelStartOffset = -1; mSelEndIndex = -1; mSelEndOffset = -1; mIteratorStatus = eIsDone; if (sInstanceCount <= 0) { sAAtom = NS_NewAtom("a"); sAddressAtom = NS_NewAtom("address"); sBigAtom = NS_NewAtom("big"); sBlinkAtom = NS_NewAtom("blink"); sBAtom = NS_NewAtom("b"); sCiteAtom = NS_NewAtom("cite"); sCodeAtom = NS_NewAtom("code"); sDfnAtom = NS_NewAtom("dfn"); sEmAtom = NS_NewAtom("em"); sFontAtom = NS_NewAtom("font"); sIAtom = NS_NewAtom("i"); sKbdAtom = NS_NewAtom("kbd"); sKeygenAtom = NS_NewAtom("keygen"); sNobrAtom = NS_NewAtom("nobr"); sSAtom = NS_NewAtom("s"); sSampAtom = NS_NewAtom("samp"); sSmallAtom = NS_NewAtom("small"); sSpacerAtom = NS_NewAtom("spacer"); sSpanAtom = NS_NewAtom("span"); sStrikeAtom = NS_NewAtom("strike"); sStrongAtom = NS_NewAtom("strong"); sSubAtom = NS_NewAtom("sub"); sSupAtom = NS_NewAtom("sup"); sTtAtom = NS_NewAtom("tt"); sUAtom = NS_NewAtom("u"); sVarAtom = NS_NewAtom("var"); sWbrAtom = NS_NewAtom("wbr"); } ++sInstanceCount; } nsTextServicesDocument::~nsTextServicesDocument() { if (mEditor && mNotifier) mEditor->RemoveEditActionListener(mNotifier); ClearOffsetTable(); if (sInstanceCount <= 1) { NS_IF_RELEASE(sAAtom); NS_IF_RELEASE(sAddressAtom); NS_IF_RELEASE(sBigAtom); NS_IF_RELEASE(sBlinkAtom); NS_IF_RELEASE(sBAtom); NS_IF_RELEASE(sCiteAtom); NS_IF_RELEASE(sCodeAtom); NS_IF_RELEASE(sDfnAtom); NS_IF_RELEASE(sEmAtom); NS_IF_RELEASE(sFontAtom); NS_IF_RELEASE(sIAtom); NS_IF_RELEASE(sKbdAtom); NS_IF_RELEASE(sKeygenAtom); NS_IF_RELEASE(sNobrAtom); NS_IF_RELEASE(sSAtom); NS_IF_RELEASE(sSampAtom); NS_IF_RELEASE(sSmallAtom); NS_IF_RELEASE(sSpacerAtom); NS_IF_RELEASE(sSpanAtom); NS_IF_RELEASE(sStrikeAtom); NS_IF_RELEASE(sStrongAtom); NS_IF_RELEASE(sSubAtom); NS_IF_RELEASE(sSupAtom); NS_IF_RELEASE(sTtAtom); NS_IF_RELEASE(sUAtom); NS_IF_RELEASE(sVarAtom); NS_IF_RELEASE(sWbrAtom); } --sInstanceCount; } #define DEBUG_TEXT_SERVICES__DOCUMENT_REFCNT 1 #ifdef DEBUG_TEXT_SERVICES__DOCUMENT_REFCNT nsrefcnt nsTextServicesDocument::AddRef(void) { return ++mRefCnt; } nsrefcnt nsTextServicesDocument::Release(void) { NS_PRECONDITION(0 != mRefCnt, "dup release"); if (--mRefCnt == 0) { NS_DELETEXPCOM(this); return 0; } return mRefCnt; } #else NS_IMPL_ADDREF(nsTextServicesDocument) NS_IMPL_RELEASE(nsTextServicesDocument) #endif NS_IMETHODIMP nsTextServicesDocument::QueryInterface(REFNSIID aIID, void** aInstancePtr) { if (nsnull == aInstancePtr) { return NS_ERROR_NULL_POINTER; } if (aIID.Equals(kISupportsIID)) { *aInstancePtr = (void*)(nsISupports*)this; NS_ADDREF_THIS(); return NS_OK; } if (aIID.Equals(kITextServicesDocumentIID)) { *aInstancePtr = (void*)(nsITextServicesDocument*)this; NS_ADDREF_THIS(); return NS_OK; } *aInstancePtr = 0; return NS_NOINTERFACE; } NS_IMETHODIMP nsTextServicesDocument::InitWithDocument(nsIDOMDocument *aDOMDocument, nsIPresShell *aPresShell) { nsresult result = NS_OK; if (!aDOMDocument || !aPresShell) return NS_ERROR_NULL_POINTER; NS_ASSERTION(!mSelCon, "mSelCon already initialized!"); if (mSelCon) return NS_ERROR_FAILURE; NS_ASSERTION(!mDOMDocument, "mDOMDocument already initialized!"); if (mDOMDocument) return NS_ERROR_FAILURE; LOCK_DOC(this); mSelCon = do_QueryInterface(aPresShell); mDOMDocument = do_QueryInterface(aDOMDocument); result = CreateDocumentContentIterator(getter_AddRefs(mIterator)); if (NS_FAILED(result)) { UNLOCK_DOC(this); return result; } mIteratorStatus = nsTextServicesDocument::eIsDone; result = FirstBlock(); UNLOCK_DOC(this); return result; } NS_IMETHODIMP nsTextServicesDocument::InitWithEditor(nsIEditor *aEditor) { nsresult result = NS_OK; nsCOMPtr selCon; nsCOMPtr doc; if (!aEditor) return NS_ERROR_NULL_POINTER; LOCK_DOC(this); // Check to see if we already have an mSelCon. If we do, it // better be the same one the editor uses! result = aEditor->GetSelectionController(getter_AddRefs(selCon)); if (NS_FAILED(result)) { UNLOCK_DOC(this); return result; } if (!selCon || (mSelCon && selCon != mSelCon)) { UNLOCK_DOC(this); return NS_ERROR_FAILURE; } if (!mSelCon) mSelCon = selCon; // Check to see if we already have an mDOMDocument. If we do, it // better be the same one the editor uses! result = aEditor->GetDocument(getter_AddRefs(doc)); if (NS_FAILED(result)) { UNLOCK_DOC(this); return result; } if (!doc || (mDOMDocument && doc != mDOMDocument)) { UNLOCK_DOC(this); return NS_ERROR_FAILURE; } if (!mDOMDocument) { mDOMDocument = doc; result = CreateDocumentContentIterator(getter_AddRefs(mIterator)); if (NS_FAILED(result)) { UNLOCK_DOC(this); return result; } mIteratorStatus = nsTextServicesDocument::eIsDone; result = FirstBlock(); if (NS_FAILED(result)) { UNLOCK_DOC(this); return result; } } mEditor = do_QueryInterface(aEditor); nsTSDNotifier *notifier = new nsTSDNotifier(this); if (!notifier) { UNLOCK_DOC(this); return NS_ERROR_OUT_OF_MEMORY; } mNotifier = do_QueryInterface(notifier); result = mEditor->AddEditActionListener(mNotifier); UNLOCK_DOC(this); return result; } NS_IMETHODIMP nsTextServicesDocument::CanEdit(PRBool *aCanEdit) { if (!aCanEdit) return NS_ERROR_NULL_POINTER; *aCanEdit = (mEditor) ? PR_TRUE : PR_FALSE; return NS_OK; } NS_IMETHODIMP nsTextServicesDocument::GetCurrentTextBlock(nsString *aStr) { nsresult result; if (!aStr) return NS_ERROR_NULL_POINTER; aStr->SetLength(0); if (!mIterator) return NS_ERROR_FAILURE; LOCK_DOC(this); result = CreateOffsetTable(aStr); UNLOCK_DOC(this); return result; } NS_IMETHODIMP nsTextServicesDocument::FirstBlock() { nsresult result; LOCK_DOC(this); mIteratorStatus = nsTextServicesDocument::eIsDone; if (!mIterator) { UNLOCK_DOC(this); return NS_ERROR_FAILURE; } // Position the iterator on the first text node // we run across: result = mIterator->First(); if (NS_FAILED(result)) { UNLOCK_DOC(this); return result; } nsCOMPtr content; while (NS_ENUMERATOR_FALSE == mIterator->IsDone()) { result = mIterator->CurrentNode(getter_AddRefs(content)); if (NS_FAILED(result)) { UNLOCK_DOC(this); return result; } if (IsTextNode(content)) { mIteratorStatus = nsTextServicesDocument::eValid; break; } result = mIterator->Next(); if (NS_FAILED(result)) return result; } // Keep track of prev and next blocks, just in case // the text service blows away the current block. if (mIteratorStatus == nsTextServicesDocument::eValid) { mPrevTextBlock = nsCOMPtr(); result = GetFirstTextNodeInNextBlock(getter_AddRefs(mNextTextBlock)); } else { // There's no text block in the document! mPrevTextBlock = nsCOMPtr(); mNextTextBlock = nsCOMPtr(); } UNLOCK_DOC(this); return result; } NS_IMETHODIMP nsTextServicesDocument::LastBlock() { nsresult result; LOCK_DOC(this); mIteratorStatus = nsTextServicesDocument::eIsDone; if (!mIterator) { UNLOCK_DOC(this); return NS_ERROR_FAILURE; } // Position the iterator on the last text node // in the tree, then walk backwards over adjacent // text nodes until we hit a block boundary: result = mIterator->Last(); if (NS_FAILED(result)) { UNLOCK_DOC(this); return result; } nsCOMPtr content; nsCOMPtr last; while (NS_ENUMERATOR_FALSE == mIterator->IsDone()) { result = mIterator->CurrentNode(getter_AddRefs(content)); if (NS_FAILED(result)) { UNLOCK_DOC(this); return result; } if (IsTextNode(content)) { result = FirstTextNodeInCurrentBlock(mIterator); if (NS_FAILED(result)) { UNLOCK_DOC(this); return result; } mIteratorStatus = nsTextServicesDocument::eValid; break; } result = mIterator->Prev(); if (NS_FAILED(result)) return result; } // Keep track of prev and next blocks, just in case // the text service blows away the current block. if (mIteratorStatus == nsTextServicesDocument::eValid) { result = GetFirstTextNodeInPrevBlock(getter_AddRefs(mPrevTextBlock)); mNextTextBlock = nsCOMPtr(); } else { // There's no text block in the document! mPrevTextBlock = nsCOMPtr(); mNextTextBlock = nsCOMPtr(); } UNLOCK_DOC(this); return result; } // XXX: CODE BLOAT ALERT. FirstSelectedBlock() and LastSelectedBlock() // are almost identical! Later, when we have time, we should try // and combine them into one method. NS_IMETHODIMP nsTextServicesDocument::FirstSelectedBlock(TSDBlockSelectionStatus *aSelStatus, PRInt32 *aSelOffset, PRInt32 *aSelLength) { nsresult result = NS_OK; if (!aSelStatus || !aSelOffset || !aSelLength) return NS_ERROR_NULL_POINTER; LOCK_DOC(this); mIteratorStatus = nsTextServicesDocument::eIsDone; *aSelStatus = nsITextServicesDocument::eBlockNotFound; *aSelOffset = *aSelLength = -1; if (!mSelCon || !mIterator) { UNLOCK_DOC(this); return NS_ERROR_FAILURE; } nsCOMPtr selection; PRBool isCollapsed = PR_FALSE; result = mSelCon->GetSelection(nsISelectionController::SELECTION_NORMAL, getter_AddRefs(selection)); if (NS_FAILED(result)) { UNLOCK_DOC(this); return result; } result = selection->GetIsCollapsed( &isCollapsed); if (NS_FAILED(result)) { UNLOCK_DOC(this); return result; } nsCOMPtr iter; nsCOMPtr range; nsCOMPtr parent; nsCOMPtr content; PRInt32 i, rangeCount, offset; if (isCollapsed) { // We have a caret. Check if the caret is in a text node. // If it is, make the text node's block the current block. // If the caret isn't in a text node, search backwards in // the document, till we find a text node. result = selection->GetRangeAt(0, getter_AddRefs(range)); if (NS_FAILED(result)) { UNLOCK_DOC(this); return result; } if (!range) { UNLOCK_DOC(this); return NS_ERROR_FAILURE; } result = range->GetStartContainer(getter_AddRefs(parent)); if (NS_FAILED(result)) { UNLOCK_DOC(this); return result; } if (!parent) { UNLOCK_DOC(this); return NS_ERROR_FAILURE; } result = range->GetStartOffset(&offset); if (NS_FAILED(result)) { UNLOCK_DOC(this); return result; } if (IsTextNode(parent)) { // The caret is in a text node. Find the beginning // of the text block containing this text node and // return. content = do_QueryInterface(parent); if (!content) { UNLOCK_DOC(this); return NS_ERROR_FAILURE; } result = mIterator->PositionAt(content); if (NS_FAILED(result)) { UNLOCK_DOC(this); return result; } result = FirstTextNodeInCurrentBlock(mIterator); if (NS_FAILED(result)) { UNLOCK_DOC(this); return result; } mIteratorStatus = nsTextServicesDocument::eValid; result = CreateOffsetTable(); if (NS_FAILED(result)) { UNLOCK_DOC(this); return result; } result = GetSelection(aSelStatus, aSelOffset, aSelLength); if (NS_FAILED(result)) { UNLOCK_DOC(this); return result; } if (*aSelStatus == nsITextServicesDocument::eBlockContains) result = SetSelectionInternal(*aSelOffset, *aSelLength, PR_FALSE); } else { // The caret isn't in a text node. Create an iterator // based on a range that extends from the beginning of // the document, to the current caret position, then // walk backwards till you find a text node, then find // the beginning of the block. result = CreateDocumentContentRootToNodeOffsetRange(parent, offset, PR_TRUE, getter_AddRefs(range)); if (NS_FAILED(result)) { UNLOCK_DOC(this); return result; } result = range->GetCollapsed(&isCollapsed); if (NS_FAILED(result)) { UNLOCK_DOC(this); return result; } if (isCollapsed) { // If we get here, the range is collapsed because there is nothing before // the caret! Just return NS_OK; UNLOCK_DOC(this); return NS_OK; } result = CreateContentIterator(range, getter_AddRefs(iter)); if (NS_FAILED(result)) { UNLOCK_DOC(this); return result; } result = iter->Last(); if (NS_FAILED(result)) { UNLOCK_DOC(this); return result; } while (iter->IsDone() == NS_ENUMERATOR_FALSE) { result = iter->CurrentNode(getter_AddRefs(content)); if (NS_FAILED(result)) { UNLOCK_DOC(this); return result; } if (!content) { UNLOCK_DOC(this); return NS_ERROR_FAILURE; } if (IsTextNode(content)) break; content = nsCOMPtr(); result = iter->Prev(); if (NS_FAILED(result)) return result; } if (!content) { UNLOCK_DOC(this); return NS_OK; } result = mIterator->PositionAt(content); if (NS_FAILED(result)) { UNLOCK_DOC(this); return result; } result = FirstTextNodeInCurrentBlock(mIterator); if (NS_FAILED(result)) { UNLOCK_DOC(this); return result; } mIteratorStatus = nsTextServicesDocument::eValid; result = CreateOffsetTable(); if (NS_FAILED(result)) { UNLOCK_DOC(this); return result; } result = GetSelection(aSelStatus, aSelOffset, aSelLength); if (NS_FAILED(result)) { UNLOCK_DOC(this); return result; } } UNLOCK_DOC(this); return result; } // If we get here, we have an uncollapsed selection! // Look through each range in the selection till you // find the first text node. If you find one, find the // beginning of it's text block, and make it the current // block. result = selection->GetRangeCount(&rangeCount); if (NS_FAILED(result)) { UNLOCK_DOC(this); return result; } NS_ASSERTION(rangeCount > 0, "Unexpected range count!"); if (rangeCount <= 0) { UNLOCK_DOC(this); return NS_OK; } // XXX: We may need to add some code here to make sure // the ranges are sorted in document appearance order! for (i = 0; i < rangeCount; i++) { // Get the i'th range from the selection. result = selection->GetRangeAt(i, getter_AddRefs(range)); if (NS_FAILED(result)) { UNLOCK_DOC(this); return result; } // Create an iterator for the range. result = CreateContentIterator(range, getter_AddRefs(iter)); if (NS_FAILED(result)) { UNLOCK_DOC(this); return result; } result = iter->First(); if (NS_FAILED(result)) { UNLOCK_DOC(this); return result; } // Now walk through the range till we find a text node. while (iter->IsDone() == NS_ENUMERATOR_FALSE) { result = iter->CurrentNode(getter_AddRefs(content)); if (NS_FAILED(result)) { UNLOCK_DOC(this); return result; } if (IsTextNode(content)) { // We found a text node, so position the document's // iterator at the beginning of the block, then get // the selection in terms of the string offset. result = mIterator->PositionAt(content); if (NS_FAILED(result)) { UNLOCK_DOC(this); return result; } result = FirstTextNodeInCurrentBlock(mIterator); if (NS_FAILED(result)) { UNLOCK_DOC(this); return result; } mIteratorStatus = nsTextServicesDocument::eValid; result = CreateOffsetTable(); if (NS_FAILED(result)) { UNLOCK_DOC(this); return result; } result = GetSelection(aSelStatus, aSelOffset, aSelLength); UNLOCK_DOC(this); return result; } result = iter->Next(); if (NS_FAILED(result)) return result; } } // If we get here, we didn't find any text node in the selection! // Create a range that extends from the beginning of the selection, // to the beginning of the document, then iterate backwards through // it till you find a text node! result = selection->GetRangeAt(0, getter_AddRefs(range)); if (NS_FAILED(result)) { UNLOCK_DOC(this); return result; } if (!range) { UNLOCK_DOC(this); return NS_ERROR_FAILURE; } result = range->GetStartContainer(getter_AddRefs(parent)); if (NS_FAILED(result)) { UNLOCK_DOC(this); return result; } if (!parent) { UNLOCK_DOC(this); return NS_ERROR_FAILURE; } result = range->GetStartOffset(&offset); if (NS_FAILED(result)) { UNLOCK_DOC(this); return result; } result = CreateDocumentContentRootToNodeOffsetRange(parent, offset, PR_TRUE, getter_AddRefs(range)); if (NS_FAILED(result)) { UNLOCK_DOC(this); return result; } result = range->GetCollapsed(&isCollapsed); if (NS_FAILED(result)) { UNLOCK_DOC(this); return result; } if (isCollapsed) { // If we get here, the range is collapsed because there is nothing before // the current selection! Just return NS_OK; UNLOCK_DOC(this); return NS_OK; } result = CreateContentIterator(range, getter_AddRefs(iter)); if (NS_FAILED(result)) { UNLOCK_DOC(this); return result; } result = iter->Last(); if (NS_FAILED(result)) { UNLOCK_DOC(this); return result; } while (iter->IsDone() == NS_ENUMERATOR_FALSE) { result = iter->CurrentNode(getter_AddRefs(content)); if (NS_FAILED(result)) { UNLOCK_DOC(this); return result; } if (IsTextNode(content)) { // We found a text node! Adjust the document's iterator to point // to the beginning of it's text block, then get the current selection. result = mIterator->PositionAt(content); if (NS_FAILED(result)) { UNLOCK_DOC(this); return result; } result = FirstTextNodeInCurrentBlock(mIterator); if (NS_FAILED(result)) { UNLOCK_DOC(this); return result; } mIteratorStatus = nsTextServicesDocument::eValid; result = CreateOffsetTable(); if (NS_FAILED(result)) { UNLOCK_DOC(this); return result; } result = GetSelection(aSelStatus, aSelOffset, aSelLength); UNLOCK_DOC(this); return result; } result = iter->Prev(); if (NS_FAILED(result)) return result; } // If we get here, we didn't find any block before or inside // the selection! Just return OK. UNLOCK_DOC(this); return NS_OK; } // XXX: CODE BLOAT ALERT. FirstSelectedBlock() and LastSelectedBlock() // are almost identical! Later, when we have time, we should try // and combine them into one method. NS_IMETHODIMP nsTextServicesDocument::LastSelectedBlock(TSDBlockSelectionStatus *aSelStatus, PRInt32 *aSelOffset, PRInt32 *aSelLength) { nsresult result = NS_OK; if (!aSelStatus || !aSelOffset || !aSelLength) return NS_ERROR_NULL_POINTER; LOCK_DOC(this); mIteratorStatus = nsTextServicesDocument::eIsDone; *aSelStatus = nsITextServicesDocument::eBlockNotFound; *aSelOffset = *aSelLength = -1; if (!mSelCon || !mIterator) { UNLOCK_DOC(this); return NS_ERROR_FAILURE; } nsCOMPtr selection; PRBool isCollapsed = PR_FALSE; result = mSelCon->GetSelection(nsISelectionController::SELECTION_NORMAL, getter_AddRefs(selection)); if (NS_FAILED(result)) { UNLOCK_DOC(this); return result; } result = selection->GetIsCollapsed(&isCollapsed); if (NS_FAILED(result)) { UNLOCK_DOC(this); return result; } nsCOMPtr iter; nsCOMPtr range; nsCOMPtr parent; nsCOMPtr content; PRInt32 i, rangeCount, offset; if (isCollapsed) { // We have a caret. Check if the caret is in a text node. // If it is, make the text node's block the current block. // If the caret isn't in a text node, search forwards in // the document, till we find a text node. result = selection->GetRangeAt(0, getter_AddRefs(range)); if (NS_FAILED(result)) { UNLOCK_DOC(this); return result; } if (!range) { UNLOCK_DOC(this); return NS_ERROR_FAILURE; } result = range->GetStartContainer(getter_AddRefs(parent)); if (NS_FAILED(result)) { UNLOCK_DOC(this); return result; } if (!parent) { UNLOCK_DOC(this); return NS_ERROR_FAILURE; } result = range->GetStartOffset(&offset); if (NS_FAILED(result)) { UNLOCK_DOC(this); return result; } if (IsTextNode(parent)) { // The caret is in a text node. Find the beginning // of the text block containing this text node and // return. content = do_QueryInterface(parent); if (!content) { UNLOCK_DOC(this); return NS_ERROR_FAILURE; } result = mIterator->PositionAt(content); if (NS_FAILED(result)) { UNLOCK_DOC(this); return result; } result = FirstTextNodeInCurrentBlock(mIterator); if (NS_FAILED(result)) { UNLOCK_DOC(this); return result; } mIteratorStatus = nsTextServicesDocument::eValid; result = CreateOffsetTable(); if (NS_FAILED(result)) { UNLOCK_DOC(this); return result; } result = GetSelection(aSelStatus, aSelOffset, aSelLength); if (NS_FAILED(result)) { UNLOCK_DOC(this); return result; } if (*aSelStatus == nsITextServicesDocument::eBlockContains) result = SetSelectionInternal(*aSelOffset, *aSelLength, PR_FALSE); } else { // The caret isn't in a text node. Create an iterator // based on a range that extends from the current caret // position to the end of the document, then walk forwards // till you find a text node, then find the beginning of it's block. result = CreateDocumentContentRootToNodeOffsetRange(parent, offset, PR_FALSE, getter_AddRefs(range)); if (NS_FAILED(result)) { UNLOCK_DOC(this); return result; } result = range->GetCollapsed(&isCollapsed); if (NS_FAILED(result)) { UNLOCK_DOC(this); return result; } if (isCollapsed) { // If we get here, the range is collapsed because there is nothing after // the caret! Just return NS_OK; UNLOCK_DOC(this); return NS_OK; } result = CreateContentIterator(range, getter_AddRefs(iter)); if (NS_FAILED(result)) { UNLOCK_DOC(this); return result; } result = iter->First(); if (NS_FAILED(result)) { UNLOCK_DOC(this); return result; } while (iter->IsDone() == NS_ENUMERATOR_FALSE) { result = iter->CurrentNode(getter_AddRefs(content)); if (NS_FAILED(result)) { UNLOCK_DOC(this); return result; } if (!content) { UNLOCK_DOC(this); return NS_ERROR_FAILURE; } if (IsTextNode(content)) break; content = nsCOMPtr(); result = iter->Next(); if (NS_FAILED(result)) return result; } if (!content) { UNLOCK_DOC(this); return NS_OK; } result = mIterator->PositionAt(content); if (NS_FAILED(result)) { UNLOCK_DOC(this); return result; } result = FirstTextNodeInCurrentBlock(mIterator); if (NS_FAILED(result)) { UNLOCK_DOC(this); return result; } mIteratorStatus = nsTextServicesDocument::eValid; result = CreateOffsetTable(); if (NS_FAILED(result)) { UNLOCK_DOC(this); return result; } result = GetSelection(aSelStatus, aSelOffset, aSelLength); if (NS_FAILED(result)) { UNLOCK_DOC(this); return result; } } UNLOCK_DOC(this); return result; } // If we get here, we have an uncollapsed selection! // Look backwards through each range in the selection till you // find the first text node. If you find one, find the // beginning of it's text block, and make it the current // block. result = selection->GetRangeCount(&rangeCount); if (NS_FAILED(result)) { UNLOCK_DOC(this); return result; } NS_ASSERTION(rangeCount > 0, "Unexpected range count!"); if (rangeCount <= 0) { UNLOCK_DOC(this); return NS_OK; } // XXX: We may need to add some code here to make sure // the ranges are sorted in document appearance order! for (i = rangeCount - 1; i >= 0; i--) { // Get the i'th range from the selection. result = selection->GetRangeAt(i, getter_AddRefs(range)); if (NS_FAILED(result)) { UNLOCK_DOC(this); return result; } // Create an iterator for the range. result = CreateContentIterator(range, getter_AddRefs(iter)); if (NS_FAILED(result)) { UNLOCK_DOC(this); return result; } result = iter->Last(); if (NS_FAILED(result)) { UNLOCK_DOC(this); return result; } // Now walk through the range till we find a text node. while (iter->IsDone() == NS_ENUMERATOR_FALSE) { result = iter->CurrentNode(getter_AddRefs(content)); if (NS_FAILED(result)) { UNLOCK_DOC(this); return result; } if (IsTextNode(content)) { // We found a text node, so position the document's // iterator at the beginning of the block, then get // the selection in terms of the string offset. result = mIterator->PositionAt(content); if (NS_FAILED(result)) { UNLOCK_DOC(this); return result; } result = FirstTextNodeInCurrentBlock(mIterator); if (NS_FAILED(result)) { UNLOCK_DOC(this); return result; } mIteratorStatus = nsTextServicesDocument::eValid; result = CreateOffsetTable(); if (NS_FAILED(result)) { UNLOCK_DOC(this); return result; } result = GetSelection(aSelStatus, aSelOffset, aSelLength); UNLOCK_DOC(this); return result; } result = iter->Prev(); if (NS_FAILED(result)) return result; } } // If we get here, we didn't find any text node in the selection! // Create a range that extends from the end of the selection, // to the end of the document, then iterate forwards through // it till you find a text node! result = selection->GetRangeAt(rangeCount - 1, getter_AddRefs(range)); if (NS_FAILED(result)) { UNLOCK_DOC(this); return result; } if (!range) { UNLOCK_DOC(this); return NS_ERROR_FAILURE; } result = range->GetEndContainer(getter_AddRefs(parent)); if (NS_FAILED(result)) { UNLOCK_DOC(this); return result; } if (!parent) { UNLOCK_DOC(this); return NS_ERROR_FAILURE; } result = range->GetEndOffset(&offset); if (NS_FAILED(result)) { UNLOCK_DOC(this); return result; } result = CreateDocumentContentRootToNodeOffsetRange(parent, offset, PR_FALSE, getter_AddRefs(range)); if (NS_FAILED(result)) { UNLOCK_DOC(this); return result; } result = range->GetCollapsed(&isCollapsed); if (NS_FAILED(result)) { UNLOCK_DOC(this); return result; } if (isCollapsed) { // If we get here, the range is collapsed because there is nothing after // the current selection! Just return NS_OK; UNLOCK_DOC(this); return NS_OK; } result = CreateContentIterator(range, getter_AddRefs(iter)); if (NS_FAILED(result)) { UNLOCK_DOC(this); return result; } result = iter->First(); if (NS_FAILED(result)) { UNLOCK_DOC(this); return result; } while (iter->IsDone() == NS_ENUMERATOR_FALSE) { result = iter->CurrentNode(getter_AddRefs(content)); if (NS_FAILED(result)) { UNLOCK_DOC(this); return result; } if (IsTextNode(content)) { // We found a text node! Adjust the document's iterator to point // to the beginning of it's text block, then get the current selection. result = mIterator->PositionAt(content); if (NS_FAILED(result)) { UNLOCK_DOC(this); return result; } result = FirstTextNodeInCurrentBlock(mIterator); if (NS_FAILED(result)) { UNLOCK_DOC(this); return result; } mIteratorStatus = nsTextServicesDocument::eValid; result = CreateOffsetTable(); if (NS_FAILED(result)) { UNLOCK_DOC(this); return result; } result = GetSelection(aSelStatus, aSelOffset, aSelLength); UNLOCK_DOC(this); return result; } result = iter->Next(); if (NS_FAILED(result)) return result; } // If we get here, we didn't find any block before or inside // the selection! Just return OK. UNLOCK_DOC(this); return NS_OK; } NS_IMETHODIMP nsTextServicesDocument::PrevBlock() { nsresult result = NS_OK; if (!mIterator) return NS_ERROR_FAILURE; LOCK_DOC(this); if (mIteratorStatus == nsTextServicesDocument::eIsDone) return NS_OK; switch (mIteratorStatus) { case nsTextServicesDocument::eValid: case nsTextServicesDocument::eNext: result = FirstTextNodeInPrevBlock(mIterator); if (NS_FAILED(result)) { mIteratorStatus = nsTextServicesDocument::eIsDone; UNLOCK_DOC(this); return result; } if (mIterator->IsDone() != NS_ENUMERATOR_FALSE) { mIteratorStatus = nsTextServicesDocument::eIsDone; UNLOCK_DOC(this); return NS_OK; } mIteratorStatus = nsTextServicesDocument::eValid; break; case nsTextServicesDocument::ePrev: // The iterator already points to the previous // block, so don't do anything. mIteratorStatus = nsTextServicesDocument::eValid; break; default: mIteratorStatus = nsTextServicesDocument::eIsDone; break; } // Keep track of prev and next blocks, just in case // the text service blows away the current block. if (mIteratorStatus == nsTextServicesDocument::eValid) { result = GetFirstTextNodeInPrevBlock(getter_AddRefs(mPrevTextBlock)); result = GetFirstTextNodeInNextBlock(getter_AddRefs(mNextTextBlock)); } else { // We must be done! mPrevTextBlock = nsCOMPtr(); mNextTextBlock = nsCOMPtr(); } UNLOCK_DOC(this); return result; } NS_IMETHODIMP nsTextServicesDocument::NextBlock() { nsresult result = NS_OK; if (!mIterator) return NS_ERROR_FAILURE; LOCK_DOC(this); if (mIteratorStatus == nsTextServicesDocument::eIsDone) return NS_OK; switch (mIteratorStatus) { case nsTextServicesDocument::eValid: // Advance the iterator to the next text block. result = FirstTextNodeInNextBlock(mIterator); if (NS_FAILED(result)) { mIteratorStatus = nsTextServicesDocument::eIsDone; UNLOCK_DOC(this); return result; } if (mIterator->IsDone() != NS_ENUMERATOR_FALSE) { mIteratorStatus = nsTextServicesDocument::eIsDone; UNLOCK_DOC(this); return NS_OK; } mIteratorStatus = nsTextServicesDocument::eValid; break; case nsTextServicesDocument::eNext: // The iterator already points to the next block, // so don't do anything to it! mIteratorStatus = nsTextServicesDocument::eValid; break; case nsTextServicesDocument::ePrev: // If the iterator is pointing to the previous block, // we know that there is no next text block! Just // fall through to the default case! default: mIteratorStatus = nsTextServicesDocument::eIsDone; break; } // Keep track of prev and next blocks, just in case // the text service blows away the current block. if (mIteratorStatus == nsTextServicesDocument::eValid) { result = GetFirstTextNodeInPrevBlock(getter_AddRefs(mPrevTextBlock)); result = GetFirstTextNodeInNextBlock(getter_AddRefs(mNextTextBlock)); } else { // We must be done. mPrevTextBlock = nsCOMPtr(); mNextTextBlock = nsCOMPtr(); } UNLOCK_DOC(this); return result; } NS_IMETHODIMP nsTextServicesDocument::IsDone(PRBool *aIsDone) { if (!aIsDone) return NS_ERROR_NULL_POINTER; *aIsDone = PR_FALSE; if (!mIterator) return NS_ERROR_FAILURE; LOCK_DOC(this); *aIsDone = (mIteratorStatus == nsTextServicesDocument::eIsDone) ? PR_TRUE : PR_FALSE; UNLOCK_DOC(this); return NS_OK; } NS_IMETHODIMP nsTextServicesDocument::SetSelection(PRInt32 aOffset, PRInt32 aLength) { nsresult result; if (!mSelCon || aOffset < 0 || aLength < 0) return NS_ERROR_FAILURE; LOCK_DOC(this); result = SetSelectionInternal(aOffset, aLength, PR_TRUE); UNLOCK_DOC(this); //**** KDEBUG **** // printf("\n * Sel: (%2d, %4d) (%2d, %4d)\n", mSelStartIndex, mSelStartOffset, mSelEndIndex, mSelEndOffset); //**** KDEBUG **** return result; } NS_IMETHODIMP nsTextServicesDocument::ScrollSelectionIntoView() { nsresult result; if (!mSelCon) return NS_ERROR_FAILURE; LOCK_DOC(this); result = mSelCon->ScrollSelectionIntoView(nsISelectionController::SELECTION_NORMAL, nsISelectionController::SELECTION_FOCUS_REGION); UNLOCK_DOC(this); return result; } NS_IMETHODIMP nsTextServicesDocument::DeleteSelection() { nsresult result = NS_OK; // We don't allow deletion during a collapsed selection! NS_ASSERTION(mEditor, "DeleteSelection called without an editor present!"); NS_ASSERTION(SelectionIsValid(), "DeleteSelection called without a valid selection!"); if (!mEditor || !SelectionIsValid()) return NS_ERROR_FAILURE; if (SelectionIsCollapsed()) return NS_OK; LOCK_DOC(this); //**** KDEBUG **** // printf("\n---- Before Delete\n"); // printf("Sel: (%2d, %4d) (%2d, %4d)\n", mSelStartIndex, mSelStartOffset, mSelEndIndex, mSelEndOffset); // PrintOffsetTable(); //**** KDEBUG **** PRInt32 i, selLength; OffsetEntry *entry, *newEntry; for (i = mSelStartIndex; i <= mSelEndIndex; i++) { entry = (OffsetEntry *)mOffsetTable[i]; if (i == mSelStartIndex) { // Calculate the length of the selection. Note that the // selection length can be zero if the start of the selection // is at the very end of a text node entry. if (entry->mIsInsertedText) { // Inserted text offset entries have no width when // talking in terms of string offsets! If the beginning // of the selection is in an inserted text offset entry, // the caret is always at the end of the entry! selLength = 0; } else selLength = entry->mLength - (mSelStartOffset - entry->mStrOffset); if (selLength > 0 && mSelStartOffset > entry->mStrOffset) { // Selection doesn't start at the beginning of the // text node entry. We need to split this entry into // two pieces, the piece before the selection, and // the piece inside the selection. result = SplitOffsetEntry(i, selLength); if (NS_FAILED(result)) { UNLOCK_DOC(this); return result; } // Adjust selection indexes to account for new entry: ++mSelStartIndex; ++mSelEndIndex; ++i; entry = (OffsetEntry *)mOffsetTable[i]; } if (selLength > 0 && mSelStartIndex < mSelEndIndex) { // The entire entry is contained in the selection. Mark the // entry invalid. entry->mIsValid = PR_FALSE; } } //**** KDEBUG **** // printf("\n---- Middle Delete\n"); // printf("Sel: (%2d, %4d) (%2d, %4d)\n", mSelStartIndex, mSelStartOffset, mSelEndIndex, mSelEndOffset); // PrintOffsetTable(); //**** KDEBUG **** if (i == mSelEndIndex) { if (entry->mIsInsertedText) { // Inserted text offset entries have no width when // talking in terms of string offsets! If the end // of the selection is in an inserted text offset entry, // the selection includes the entire entry! entry->mIsValid = PR_FALSE; } else { // Calculate the length of the selection. Note that the // selection length can be zero if the end of the selection // is at the very beginning of a text node entry. selLength = mSelEndOffset - entry->mStrOffset; if (selLength > 0 && mSelEndOffset < entry->mStrOffset + entry->mLength) { // mStrOffset is guaranteed to be inside the selection, even // when mSelStartIndex == mSelEndIndex. result = SplitOffsetEntry(i, entry->mLength - selLength); if (NS_FAILED(result)) { UNLOCK_DOC(this); return result; } // Update the entry fields: newEntry = (OffsetEntry *)mOffsetTable[i+1]; newEntry->mNodeOffset = entry->mNodeOffset; } if (selLength > 0 && mSelEndOffset == entry->mStrOffset + entry->mLength) { // The entire entry is contained in the selection. Mark the // entry invalid. entry->mIsValid = PR_FALSE; } } } if (i != mSelStartIndex && i != mSelEndIndex) { // The entire entry is contained in the selection. Mark the // entry invalid. entry->mIsValid = PR_FALSE; } } // Make sure mIterator always points to something valid! AdjustContentIterator(); // Now delete the actual content! result = mEditor->DeleteSelection(nsIEditor::ePrevious); if (NS_FAILED(result)) { UNLOCK_DOC(this); return result; } entry = 0; // Move the caret to the end of the first valid entry. // Start with mSelStartIndex since it may still be valid. for (i = mSelStartIndex; !entry && i >= 0; i--) { entry = (OffsetEntry *)mOffsetTable[i]; if (!entry->mIsValid) entry = 0; else { mSelStartIndex = mSelEndIndex = i; mSelStartOffset = mSelEndOffset = entry->mStrOffset + entry->mLength; } } // If we still don't have a valid entry, move the caret // to the next valid entry after the selection: for (i = mSelEndIndex; !entry && i < mOffsetTable.Count(); i++) { entry = (OffsetEntry *)mOffsetTable[i]; if (!entry->mIsValid) entry = 0; else { mSelStartIndex = mSelEndIndex = i; mSelStartOffset = mSelEndOffset = entry->mStrOffset; } } if (entry) result = SetSelection(mSelStartOffset, 0); else { // Uuughh we have no valid offset entry to place our // caret ... just mark the selection invalid. mSelStartIndex = mSelEndIndex = -1; mSelStartOffset = mSelEndOffset = -1; } // Now remove any invalid entries from the offset table. result = RemoveInvalidOffsetEntries(); //**** KDEBUG **** // printf("\n---- After Delete\n"); // printf("Sel: (%2d, %4d) (%2d, %4d)\n", mSelStartIndex, mSelStartOffset, mSelEndIndex, mSelEndOffset); // PrintOffsetTable(); //**** KDEBUG **** UNLOCK_DOC(this); return result; } NS_IMETHODIMP nsTextServicesDocument::InsertText(const nsString *aText) { nsresult result = NS_OK; NS_ASSERTION(mEditor, "InsertText called without an editor present!"); if (!mEditor) return NS_ERROR_FAILURE; if (!aText) return NS_ERROR_NULL_POINTER; LOCK_DOC(this); result = mEditor->BeginTransaction(); if (NS_FAILED(result)) { UNLOCK_DOC(this); return result; } // We have to call DeleteSelection(), just in case there is // a selection, to make sure that our offset table is kept in sync! result = DeleteSelection(); if (NS_FAILED(result)) { mEditor->EndTransaction(); UNLOCK_DOC(this); return result; } nsCOMPtr textEditor (do_QueryInterface(mEditor, &result)); if (textEditor) result = textEditor->InsertText(aText->GetUnicode()); if (NS_FAILED(result)) { mEditor->EndTransaction(); UNLOCK_DOC(this); return result; } if (SelectionIsValid()) { //**** KDEBUG **** // printf("\n---- Before Insert\n"); // printf("Sel: (%2d, %4d) (%2d, %4d)\n", mSelStartIndex, mSelStartOffset, mSelEndIndex, mSelEndOffset); // PrintOffsetTable(); //**** KDEBUG **** PRInt32 i, strLength = aText->Length(); nsCOMPtr selection; OffsetEntry *itEntry; OffsetEntry *entry = (OffsetEntry *)mOffsetTable[mSelStartIndex]; void *node = entry->mNode; NS_ASSERTION((entry->mIsValid), "Invalid insertion point!"); if (entry->mStrOffset == mSelStartOffset) { if (entry->mIsInsertedText) { // If the caret is in an inserted text offset entry, // we simply insert the text at the end of the entry. entry->mLength += strLength; } else { // Insert an inserted text offset entry before the current // entry! itEntry = new OffsetEntry(entry->mNode, entry->mStrOffset, strLength); if (!itEntry) { mEditor->EndTransaction(); UNLOCK_DOC(this); return NS_ERROR_OUT_OF_MEMORY; } itEntry->mIsInsertedText = PR_TRUE; if (!mOffsetTable.InsertElementAt(itEntry, mSelStartIndex)) { mEditor->EndTransaction(); UNLOCK_DOC(this); return NS_ERROR_FAILURE; } } } else if ((entry->mStrOffset + entry->mLength) == mSelStartOffset) { // We are inserting text at the end of the current offset entry. // Look at the next valid entry in the table. If it's an inserted // text entry, add to it's length and adjust it's node offset. If // it isn't, add a new inserted text entry. i = mSelStartIndex + 1; itEntry = 0; if (mOffsetTable.Count() > i) { itEntry = (OffsetEntry *)mOffsetTable[i]; if (!itEntry) { mEditor->EndTransaction(); UNLOCK_DOC(this); return NS_ERROR_FAILURE; } // Check if the entry is a match. If it isn't, set // iEntry to zero. if (!itEntry->mIsInsertedText || itEntry->mStrOffset != mSelStartOffset) itEntry = 0; } if (!itEntry) { // We didn't find an inserted text offset entry, so // create one. itEntry = new OffsetEntry(entry->mNode, mSelStartOffset, 0); if (!itEntry) { mEditor->EndTransaction(); UNLOCK_DOC(this); return NS_ERROR_OUT_OF_MEMORY; } itEntry->mNodeOffset = entry->mNodeOffset + entry->mLength; itEntry->mIsInsertedText = PR_TRUE; if (!mOffsetTable.InsertElementAt(itEntry, i)) { delete itEntry; return NS_ERROR_FAILURE; } } // We have a valid inserted text offset entry. Update it's // length, adjust the selection indexes, and make sure the // caret is properly placed! itEntry->mLength += strLength; mSelStartIndex = mSelEndIndex = i; result = mSelCon->GetSelection(nsISelectionController::SELECTION_NORMAL, getter_AddRefs(selection)); if (NS_FAILED(result)) { mEditor->EndTransaction(); UNLOCK_DOC(this); return result; } result = selection->Collapse(itEntry->mNode, itEntry->mNodeOffset + itEntry->mLength); if (NS_FAILED(result)) { mEditor->EndTransaction(); UNLOCK_DOC(this); return result; } } else if ((entry->mStrOffset + entry->mLength) > mSelStartOffset) { // We are inserting text into the middle of the current offset entry. // split the current entry into two parts, then insert an inserted text // entry between them! i = entry->mLength - (mSelStartOffset - entry->mStrOffset); result = SplitOffsetEntry(mSelStartIndex, i); if (NS_FAILED(result)) { mEditor->EndTransaction(); UNLOCK_DOC(this); return result; } itEntry = new OffsetEntry(entry->mNode, mSelStartOffset, strLength); if (!itEntry) { mEditor->EndTransaction(); UNLOCK_DOC(this); return NS_ERROR_OUT_OF_MEMORY; } itEntry->mIsInsertedText = PR_TRUE; itEntry->mNodeOffset = entry->mNodeOffset + entry->mLength; if (!mOffsetTable.InsertElementAt(itEntry, mSelStartIndex + 1)) { mEditor->EndTransaction(); UNLOCK_DOC(this); return NS_ERROR_FAILURE; } mSelEndIndex = ++mSelStartIndex; } // We've just finished inserting an inserted text offset entry. // update all entries with the same mNode pointer that follow // it in the table! for (i = mSelStartIndex + 1; i < mOffsetTable.Count(); i++) { entry = (OffsetEntry *)mOffsetTable[i]; if (entry->mNode == node) { if (entry->mIsValid) entry->mNodeOffset += strLength; } else break; } //**** KDEBUG **** // printf("\n---- After Insert\n"); // printf("Sel: (%2d, %4d) (%2d, %4d)\n", mSelStartIndex, mSelStartOffset, mSelEndIndex, mSelEndOffset); // PrintOffsetTable(); //**** KDEBUG **** } result = mEditor->EndTransaction(); if (NS_FAILED(result)) { UNLOCK_DOC(this); return result; } UNLOCK_DOC(this); return result; } NS_IMETHODIMP nsTextServicesDocument::SetDisplayStyle(TSDDisplayStyle aStyle) { return NS_ERROR_NOT_IMPLEMENTED; } nsresult nsTextServicesDocument::InsertNode(nsIDOMNode *aNode, nsIDOMNode *aParent, PRInt32 aPosition) { //**** KDEBUG **** // printf("** InsertNode: 0x%.8x 0x%.8x %d\n", aNode, aParent, aPosition); // fflush(stdout); //**** KDEBUG **** NS_ASSERTION(0, "InsertNode called, offset tables might be out of sync."); return NS_OK; } nsresult nsTextServicesDocument::DeleteNode(nsIDOMNode *aChild) { //**** KDEBUG **** // printf("** DeleteNode: 0x%.8x\n", aChild); // fflush(stdout); //**** KDEBUG **** LOCK_DOC(this); PRInt32 nodeIndex, tcount; PRBool hasEntry; OffsetEntry *entry; nsresult result = NodeHasOffsetEntry(aChild, &hasEntry, &nodeIndex); if (NS_FAILED(result)) { UNLOCK_DOC(this); return result; } if (!hasEntry) { // It's okay if the node isn't in the offset table, the // editor could be cleaning house. UNLOCK_DOC(this); return NS_OK; } nsCOMPtr content; nsCOMPtr node; result = mIterator->CurrentNode(getter_AddRefs(content)); if (content) { node = do_QueryInterface(content); if (node && node.get() == aChild) { // The iterator points to the node that is about // to be deleted. Move it to the next valid text node // listed in the offset table! // XXX } } tcount = mOffsetTable.Count(); while (nodeIndex < tcount) { entry = (OffsetEntry *)mOffsetTable[nodeIndex]; if (!entry) { UNLOCK_DOC(this); return NS_ERROR_FAILURE; } if (entry->mNode == aChild) { entry->mIsValid = PR_FALSE; } nodeIndex++; } UNLOCK_DOC(this); NS_ASSERTION(0, "DeleteNode called, offset tables might be out of sync."); return NS_OK; } nsresult nsTextServicesDocument::SplitNode(nsIDOMNode *aExistingRightNode, PRInt32 aOffset, nsIDOMNode *aNewLeftNode) { //**** KDEBUG **** // printf("** SplitNode: 0x%.8x %d 0x%.8x\n", aExistingRightNode, aOffset, aNewLeftNode); // fflush(stdout); //**** KDEBUG **** NS_ASSERTION(0, "SplitNode called, offset tables might be out of sync."); return NS_OK; } nsresult nsTextServicesDocument::JoinNodes(nsIDOMNode *aLeftNode, nsIDOMNode *aRightNode, nsIDOMNode *aParent) { PRInt32 i; PRUint16 type; nsresult result; //**** KDEBUG **** // printf("** JoinNodes: 0x%.8x 0x%.8x 0x%.8x\n", aLeftNode, aRightNode, aParent); // fflush(stdout); //**** KDEBUG **** // Make sure that both nodes are text nodes! result = aLeftNode->GetNodeType(&type); if (NS_FAILED(result)) return PR_FALSE; if (nsIDOMNode::TEXT_NODE != type) { NS_ASSERTION(0, "JoinNode called with a non-text left node!"); return NS_ERROR_FAILURE; } result = aRightNode->GetNodeType(&type); if (NS_FAILED(result)) return PR_FALSE; if (nsIDOMNode::TEXT_NODE != type) { NS_ASSERTION(0, "JoinNode called with a non-text right node!"); return NS_ERROR_FAILURE; } // Note: The editor merges the contents of the left node into the // contents of the right. PRInt32 leftIndex, rightIndex; PRBool leftHasEntry, rightHasEntry; result = NodeHasOffsetEntry(aLeftNode, &leftHasEntry, &leftIndex); if (NS_FAILED(result)) return result; if (!leftHasEntry) { // XXX: Not sure if we should be throwing an error here! NS_ASSERTION(0, "JoinNode called with node not listed in offset table."); return NS_ERROR_FAILURE; } result = NodeHasOffsetEntry(aRightNode, &rightHasEntry, &rightIndex); if (NS_FAILED(result)) return result; if (!rightHasEntry) { // XXX: Not sure if we should be throwing an error here! NS_ASSERTION(0, "JoinNode called with node not listed in offset table."); return NS_ERROR_FAILURE; } NS_ASSERTION(leftIndex < rightIndex, "Indexes out of order."); if (leftIndex > rightIndex) { // Don't know how to handle this situation. return NS_ERROR_FAILURE; } LOCK_DOC(this); OffsetEntry *entry = (OffsetEntry *)mOffsetTable[leftIndex]; NS_ASSERTION(entry->mNodeOffset == 0, "Unexpected offset value for leftIndex."); entry = (OffsetEntry *)mOffsetTable[rightIndex]; NS_ASSERTION(entry->mNodeOffset == 0, "Unexpected offset value for rightIndex."); // Run through the table and change all entries referring to // the left node so that they now refer to the right node: PRInt32 nodeLength = 0; for (i = leftIndex; i < rightIndex; i++) { entry = (OffsetEntry *)mOffsetTable[i]; if (entry->mNode == aLeftNode) { if (entry->mIsValid) { entry->mNode = aRightNode; nodeLength += entry->mLength; } } else break; } // Run through the table and adjust the node offsets // for all entries referring to the right node. for (i = rightIndex; i < mOffsetTable.Count(); i++) { entry = (OffsetEntry *)mOffsetTable[i]; if (entry->mNode == aRightNode) { if (entry->mIsValid) entry->mNodeOffset += nodeLength; } else break; } // Now check to see if the iterator is pointing to the // left node. If it is, make it point to the right node! nsCOMPtr currentContent; nsCOMPtr leftContent = do_QueryInterface(aLeftNode); nsCOMPtr rightContent = do_QueryInterface(aRightNode); if (!leftContent || !rightContent) { UNLOCK_DOC(this); return NS_ERROR_FAILURE; } result = mIterator->CurrentNode(getter_AddRefs(currentContent)); if (NS_FAILED(result)) { UNLOCK_DOC(this); return result; } if (currentContent == leftContent) result = mIterator->PositionAt(rightContent); UNLOCK_DOC(this); return NS_OK; } nsresult nsTextServicesDocument::CreateContentIterator(nsIDOMRange *aRange, nsIContentIterator **aIterator) { nsresult result; if (!aRange || !aIterator) return NS_ERROR_NULL_POINTER; *aIterator = 0; result = nsComponentManager::CreateInstance(kCContentIteratorCID, nsnull, NS_GET_IID(nsIContentIterator), (void **)aIterator); if (NS_FAILED(result)) return result; if (!*aIterator) return NS_ERROR_NULL_POINTER; result = (*aIterator)->Init(aRange); if (NS_FAILED(result)) { NS_RELEASE((*aIterator)); *aIterator = 0; return result; } return NS_OK; } nsresult nsTextServicesDocument::GetDocumentContentRootNode(nsIDOMNode **aNode) { nsresult result; if (!aNode) return NS_ERROR_NULL_POINTER; *aNode = 0; if (!mDOMDocument) return NS_ERROR_FAILURE; nsCOMPtr htmlDoc = do_QueryInterface(mDOMDocument); if (htmlDoc) { // For HTML documents, the content root node is the body. nsCOMPtr bodyElement; result = htmlDoc->GetBody(getter_AddRefs(bodyElement)); if (NS_FAILED(result)) return result; if (!bodyElement) return NS_ERROR_FAILURE; result = bodyElement->QueryInterface(NS_GET_IID(nsIDOMNode), (void **)aNode); } else { // For non-HTML documents, the content root node will be the document element. nsCOMPtr docElement; result = mDOMDocument->GetDocumentElement(getter_AddRefs(docElement)); if (NS_FAILED(result)) return result; if (!docElement) return NS_ERROR_FAILURE; result = docElement->QueryInterface(NS_GET_IID(nsIDOMNode), (void **)aNode); } return result; } nsresult nsTextServicesDocument::CreateDocumentContentRange(nsIDOMRange **aRange) { nsresult result; if (!aRange) return NS_ERROR_NULL_POINTER; *aRange = 0; nsCOMPtrnode; result = GetDocumentContentRootNode(getter_AddRefs(node)); if (NS_FAILED(result)) return result; if (!node) return NS_ERROR_NULL_POINTER; result = nsComponentManager::CreateInstance(kCDOMRangeCID, nsnull, NS_GET_IID(nsIDOMRange), (void **)aRange); if (NS_FAILED(result)) return result; if (!*aRange) return NS_ERROR_NULL_POINTER; result = (*aRange)->SelectNodeContents(node); if (NS_FAILED(result)) { NS_RELEASE((*aRange)); *aRange = 0; return result; } return NS_OK; } nsresult nsTextServicesDocument::CreateDocumentContentRootToNodeOffsetRange(nsIDOMNode *aParent, PRInt32 aOffset, PRBool aToStart, nsIDOMRange **aRange) { nsresult result; if (!aParent || !aRange) return NS_ERROR_NULL_POINTER; *aRange = 0; NS_ASSERTION(aOffset >= 0, "Invalid offset!"); if (aOffset < 0) return NS_ERROR_FAILURE; nsCOMPtr bodyNode; result = GetDocumentContentRootNode(getter_AddRefs(bodyNode)); if (NS_FAILED(result)) return result; if (!bodyNode) return NS_ERROR_NULL_POINTER; nsCOMPtr startNode; nsCOMPtr endNode; PRInt32 startOffset, endOffset; if (aToStart) { // The range should begin at the start of the document // and extend up until (aParent, aOffset). startNode = bodyNode; startOffset = 0; endNode = do_QueryInterface(aParent); endOffset = aOffset; } else { // The range should begin at (aParent, aOffset) and // extend to the end of the document. nsCOMPtr nodeList; PRUint32 nodeListLength; startNode = do_QueryInterface(aParent); startOffset = aOffset; endNode = bodyNode; endOffset = 0; result = bodyNode->GetChildNodes(getter_AddRefs(nodeList)); if (NS_FAILED(result)) return NS_ERROR_FAILURE; if (nodeList) { result = nodeList->GetLength(&nodeListLength); if (NS_FAILED(result)) return NS_ERROR_FAILURE; endOffset = (PRInt32)nodeListLength; } } result = nsComponentManager::CreateInstance(kCDOMRangeCID, nsnull, NS_GET_IID(nsIDOMRange), (void **)aRange); if (NS_FAILED(result)) return result; if (!*aRange) return NS_ERROR_NULL_POINTER; result = (*aRange)->SetStart(startNode, startOffset); if (NS_SUCCEEDED(result)) result = (*aRange)->SetEnd(endNode, endOffset); if (NS_FAILED(result)) { NS_RELEASE((*aRange)); *aRange = 0; } return result; } nsresult nsTextServicesDocument::CreateDocumentContentIterator(nsIContentIterator **aIterator) { nsresult result; if (!aIterator) return NS_ERROR_NULL_POINTER; nsCOMPtr range; result = CreateDocumentContentRange(getter_AddRefs(range)); if (NS_FAILED(result)) return result; result = CreateContentIterator(range, aIterator); return result; } nsresult nsTextServicesDocument::AdjustContentIterator() { nsCOMPtr content; nsCOMPtr node; nsresult result; PRInt32 i; if (!mIterator) return NS_ERROR_FAILURE; result = mIterator->CurrentNode(getter_AddRefs(content)); if (NS_FAILED(result)) return result; if (!content) return NS_ERROR_FAILURE; node = do_QueryInterface(content); if (!node) return NS_ERROR_FAILURE; nsIDOMNode *nodePtr = node.get(); PRInt32 tcount = mOffsetTable.Count(); nsIDOMNode *prevValidNode = 0; nsIDOMNode *nextValidNode = 0; PRBool foundEntry = PR_FALSE; OffsetEntry *entry; for (i = 0; i < tcount && !nextValidNode; i++) { entry = (OffsetEntry *)mOffsetTable[i]; if (!entry) return NS_ERROR_FAILURE; if (entry->mNode == nodePtr) { if (entry->mIsValid) { // The iterator is still pointing to something valid! // Do nothing! return NS_OK; } else { // We found an invalid entry that points to // the current iterator node. Stop looking for // a previous valid node! foundEntry = PR_TRUE; } } if (entry->mIsValid) { if (!foundEntry) prevValidNode = entry->mNode; else nextValidNode = entry->mNode; } } content = nsCOMPtr(); if (prevValidNode) content = do_QueryInterface(prevValidNode); else if (nextValidNode) content = do_QueryInterface(nextValidNode); if (content) { result = mIterator->PositionAt(content); if (NS_FAILED(result)) mIteratorStatus = eIsDone; else mIteratorStatus = eValid; return result; } // If we get here, there aren't any valid entries // in the offset table! Try to position the iterator // on the next text block first, then previous if // one doesn't exist! if (mNextTextBlock) { result = mIterator->PositionAt(mNextTextBlock); if (NS_FAILED(result)) { mIteratorStatus = eIsDone; return result; } mIteratorStatus = eNext; } else if (mPrevTextBlock) { result = mIterator->PositionAt(mPrevTextBlock); if (NS_FAILED(result)) { mIteratorStatus = eIsDone; return result; } mIteratorStatus = ePrev; } else mIteratorStatus = eIsDone; return NS_OK; } PRBool nsTextServicesDocument::IsBlockNode(nsIContent *aContent) { nsCOMPtr atom; aContent->GetTag(*getter_AddRefs(atom)); if (!atom) return PR_TRUE; nsIAtom *atomPtr = atom.get(); return (sAAtom != atomPtr && sAddressAtom != atomPtr && sBigAtom != atomPtr && sBlinkAtom != atomPtr && sBAtom != atomPtr && sCiteAtom != atomPtr && sCodeAtom != atomPtr && sDfnAtom != atomPtr && sEmAtom != atomPtr && sFontAtom != atomPtr && sIAtom != atomPtr && sKbdAtom != atomPtr && sKeygenAtom != atomPtr && sNobrAtom != atomPtr && sSAtom != atomPtr && sSampAtom != atomPtr && sSmallAtom != atomPtr && sSpacerAtom != atomPtr && sSpanAtom != atomPtr && sStrikeAtom != atomPtr && sStrongAtom != atomPtr && sSubAtom != atomPtr && sSupAtom != atomPtr && sTtAtom != atomPtr && sUAtom != atomPtr && sVarAtom != atomPtr && sWbrAtom != atomPtr); } PRBool nsTextServicesDocument::HasSameBlockNodeParent(nsIContent *aContent1, nsIContent *aContent2) { nsCOMPtr p1; nsCOMPtr p2; nsresult result; result = aContent1->GetParent(*getter_AddRefs(p1)); if (NS_FAILED(result)) return PR_FALSE; result = aContent2->GetParent(*getter_AddRefs(p2)); if (NS_FAILED(result)) return PR_FALSE; // Quick test: if (p1 == p2) return PR_TRUE; // Walk up the parent hierarchy looking for closest block boundary node: nsCOMPtr tmp; while (p1 && !IsBlockNode(p1)) { result = p1->GetParent(*getter_AddRefs(tmp)); if (NS_FAILED(result)) return PR_FALSE; p1 = tmp; } while (p2 && !IsBlockNode(p2)) { result = p2->GetParent(*getter_AddRefs(tmp)); if (NS_FAILED(result)) return PR_FALSE; p2 = tmp; } return p1 == p2; } PRBool nsTextServicesDocument::IsTextNode(nsIContent *aContent) { if (!aContent) return PR_FALSE; nsCOMPtr node = do_QueryInterface(aContent); return IsTextNode(node); } PRBool nsTextServicesDocument::IsTextNode(nsIDOMNode *aNode) { if (!aNode) return PR_FALSE; PRUint16 type; nsresult result = aNode->GetNodeType(&type); if (NS_FAILED(result)) return PR_FALSE; return nsIDOMNode::TEXT_NODE == type; } nsresult nsTextServicesDocument::SetSelectionInternal(PRInt32 aOffset, PRInt32 aLength, PRBool aDoUpdate) { nsresult result = NS_OK; if (!mSelCon || aOffset < 0 || aLength < 0) return NS_ERROR_FAILURE; nsIDOMNode *sNode = 0, *eNode = 0; PRInt32 i, sOffset = 0, eOffset = 0; OffsetEntry *entry; // Find start of selection in node offset terms: for (i = 0; !sNode && i < mOffsetTable.Count(); i++) { entry = (OffsetEntry *)mOffsetTable[i]; if (entry->mIsValid) { if (entry->mIsInsertedText) { // Caret can only be placed at the end of an // inserted text offset entry, if the offsets // match exactly! if (entry->mStrOffset == aOffset) { sNode = entry->mNode; sOffset = entry->mNodeOffset + entry->mLength; } } else if (aOffset >= entry->mStrOffset && aOffset <= entry->mStrOffset + entry->mLength) { sNode = entry->mNode; sOffset = entry->mNodeOffset + aOffset - entry->mStrOffset; } if (sNode) { mSelStartIndex = i; mSelStartOffset = aOffset; } } } if (!sNode) return NS_ERROR_FAILURE; // XXX: If we ever get a SetSelection() method in nsIEditor, we should // use it. nsCOMPtr selection; if (aDoUpdate) { result = mSelCon->GetSelection(nsISelectionController::SELECTION_NORMAL, getter_AddRefs(selection)); if (NS_FAILED(result)) return result; result = selection->Collapse(sNode, sOffset); if (NS_FAILED(result)) return result; } if (aLength <= 0) { // We have a collapsed selection. (Caret) mSelEndIndex = mSelStartIndex; mSelEndOffset = mSelStartOffset; //**** KDEBUG **** // printf("\n* Sel: (%2d, %4d) (%2d, %4d)\n", mSelStartIndex, mSelStartOffset, mSelEndIndex, mSelEndOffset); //**** KDEBUG **** return NS_OK; } // Find the end of the selection in node offset terms: PRInt32 endOffset = aOffset + aLength; for (i = mOffsetTable.Count() - 1; !eNode && i >= 0; i--) { entry = (OffsetEntry *)mOffsetTable[i]; if (entry->mIsValid) { if (entry->mIsInsertedText) { if (entry->mStrOffset == eOffset) { // If the selection ends on an inserted text offset entry, // the selection includes the entire entry! eNode = entry->mNode; eOffset = entry->mNodeOffset + entry->mLength; } } else if (endOffset >= entry->mStrOffset && endOffset <= entry->mStrOffset + entry->mLength) { eNode = entry->mNode; eOffset = entry->mNodeOffset + endOffset - entry->mStrOffset; } if (eNode) { mSelEndIndex = i; mSelEndOffset = endOffset; } } } if (aDoUpdate && eNode) { result = selection->Extend(eNode, eOffset); if (NS_FAILED(result)) return result; } //**** KDEBUG **** // printf("\n * Sel: (%2d, %4d) (%2d, %4d)\n", mSelStartIndex, mSelStartOffset, mSelEndIndex, mSelEndOffset); //**** KDEBUG **** return result; } nsresult nsTextServicesDocument::GetSelection(nsITextServicesDocument::TSDBlockSelectionStatus *aSelStatus, PRInt32 *aSelOffset, PRInt32 *aSelLength) { nsresult result; if (!aSelStatus || !aSelOffset || !aSelLength) return NS_ERROR_NULL_POINTER; *aSelStatus = nsITextServicesDocument::eBlockNotFound; *aSelOffset = -1; *aSelLength = -1; if (!mDOMDocument || !mSelCon) return NS_ERROR_FAILURE; if (mIteratorStatus == nsTextServicesDocument::eIsDone) return NS_OK; nsCOMPtr selection; PRBool isCollapsed; result = mSelCon->GetSelection(nsISelectionController::SELECTION_NORMAL, getter_AddRefs(selection)); if (NS_FAILED(result)) return result; if (!selection) return NS_ERROR_FAILURE; result = selection->GetIsCollapsed(&isCollapsed); if (NS_FAILED(result)) return result; // XXX: If we expose this method publicly, we need to // add LOCK_DOC/UNLOCK_DOC calls! // LOCK_DOC(this); if (isCollapsed) result = GetCollapsedSelection(aSelStatus, aSelOffset, aSelLength); else result = GetUncollapsedSelection(aSelStatus, aSelOffset, aSelLength); // UNLOCK_DOC(this); return result; } nsresult nsTextServicesDocument::GetCollapsedSelection(nsITextServicesDocument::TSDBlockSelectionStatus *aSelStatus, PRInt32 *aSelOffset, PRInt32 *aSelLength) { nsresult result; nsCOMPtr selection; result = mSelCon->GetSelection(nsISelectionController::SELECTION_NORMAL, getter_AddRefs(selection)); if (NS_FAILED(result)) return result; if (!selection) return NS_ERROR_FAILURE; // The calling function should have done the GetIsCollapsed() // check already. Just assume it's collapsed! nsCOMPtr range; nsCOMPtr content; OffsetEntry *entry; nsCOMPtr parent; PRInt32 offset, tableCount, i; PRInt32 e1s1, e2s1; OffsetEntry *eStart, *eEnd; PRInt32 eStartOffset, eEndOffset; *aSelStatus = nsITextServicesDocument::eBlockOutside; *aSelOffset = *aSelLength = -1; tableCount = mOffsetTable.Count(); if (tableCount == 0) return NS_OK; // Get pointers to the first and last offset entries // in the table. eStart = (OffsetEntry *)mOffsetTable[0]; if (tableCount > 1) eEnd = (OffsetEntry *)mOffsetTable[tableCount - 1]; else eEnd = eStart; eStartOffset = eStart->mNodeOffset; eEndOffset = eEnd->mNodeOffset + eEnd->mLength; result = selection->GetRangeAt(0, getter_AddRefs(range)); if (NS_FAILED(result)) return result; result = range->GetStartContainer(getter_AddRefs(parent)); if (NS_FAILED(result)) return result; result = range->GetStartOffset(&offset); if (NS_FAILED(result)) return result; result = ComparePoints(eStart->mNode, eStartOffset, parent, offset, &e1s1); if (NS_FAILED(result)) return result; result = ComparePoints(eEnd->mNode, eEndOffset, parent, offset, &e2s1); if (NS_FAILED(result)) return result; if (e1s1 > 0 || e2s1 < 0) { // We're done if the caret is outside the // current text block. return NS_OK; } if (IsTextNode(parent)) { // Good news, the caret is in a text node. Look // through the offset table for the entry that // matches it's parent and offset. for (i = 0; i < tableCount; i++) { entry = (OffsetEntry *)mOffsetTable[i]; if (!entry) return NS_ERROR_FAILURE; if (entry->mNode == parent.get() && entry->mNodeOffset <= offset && offset <= (entry->mNodeOffset + entry->mLength)) { *aSelStatus = nsITextServicesDocument::eBlockContains; *aSelOffset = entry->mStrOffset + (offset - entry->mNodeOffset); *aSelLength = 0; return NS_OK; } } // If we get here, we didn't find a text node entry // in our offset table that matched. return NS_ERROR_FAILURE; } // The caret is in our text block, but it's positioned in some // non-text node (ex. ). Create a range based on the start // and end of the text block, then create an iterator based on // this range, with it's initial position set to the closest // child of this non-text node. Then look for the closest text // node. nsCOMPtr node, saveNode; nsCOMPtr children; nsCOMPtr iter; PRBool hasChildren; result = CreateRange(eStart->mNode, eStartOffset, eEnd->mNode, eEndOffset, getter_AddRefs(range)); if (NS_FAILED(result)) return result; result = CreateContentIterator(range, getter_AddRefs(iter)); if (NS_FAILED(result)) return result; result = parent->HasChildNodes(&hasChildren); if (NS_FAILED(result)) return result; if (hasChildren) { // XXX: We need to make sure that all of parent's // children are in the text block. // If the parent has children, position the iterator // on the child that is to the left of the offset. PRUint32 childIndex = (PRUint32)offset; result = parent->GetChildNodes(getter_AddRefs(children)); if (NS_FAILED(result)) return result; if (!children) return NS_ERROR_FAILURE; if (childIndex > 0) { PRUint32 numChildren; result = children->GetLength(&numChildren); if (NS_FAILED(result)) return result; NS_ASSERTION(childIndex <= numChildren, "Invalid selection offset!"); if (childIndex > numChildren) childIndex = numChildren; childIndex -= 1; } result = children->Item(childIndex, getter_AddRefs(saveNode)); if (NS_FAILED(result)) return result; content = do_QueryInterface(saveNode); if (!content) return NS_ERROR_FAILURE; result = iter->PositionAt(content); if (NS_FAILED(result)) return result; } else { // The parent has no children, so position the iterator // on the parent. content = do_QueryInterface(parent); if (!content) return NS_ERROR_FAILURE; result = iter->PositionAt(content); if (NS_FAILED(result)) return result; saveNode = parent; } // Now iterate to the left, towards the beginning of // the text block, to find the first text node you // come across. while (iter->IsDone() == NS_ENUMERATOR_FALSE) { result = iter->CurrentNode(getter_AddRefs(content)); if (NS_FAILED(result)) return result; if (IsTextNode(content)) { node = do_QueryInterface(content); if (!node) return NS_ERROR_FAILURE; break; } node = nsCOMPtr(); result = iter->Prev(); if (NS_FAILED(result)) return result; } if (node) { // We found a node, now set the offset to the end // of the text node. nsString str; result = node->GetNodeValue(str); if (NS_FAILED(result)) return result; offset = str.Length(); } else { // We should never really get here, but I'm paranoid. // We didn't find a text node above, so iterate to // the right, towards the end of the text block, looking // for a text node. content = do_QueryInterface(saveNode); result = iter->PositionAt(content); if (NS_FAILED(result)) return result; while (iter->IsDone() == NS_ENUMERATOR_FALSE) { result = iter->CurrentNode(getter_AddRefs(content)); if (NS_FAILED(result)) return result; if (IsTextNode(content)) { node = do_QueryInterface(content); if (!node) return NS_ERROR_FAILURE; break; } node = nsCOMPtr(); result = iter->Next(); if (NS_FAILED(result)) return result; } if (!node) return NS_ERROR_FAILURE; // We found a text node, so set the offset to // the begining of the node. offset = 0; } for (i = 0; i < tableCount; i++) { entry = (OffsetEntry *)mOffsetTable[i]; if (!entry) return NS_ERROR_FAILURE; if (entry->mNode == node.get() && entry->mNodeOffset <= offset && offset <= (entry->mNodeOffset + entry->mLength)) { *aSelStatus = nsITextServicesDocument::eBlockContains; *aSelOffset = entry->mStrOffset + (offset - entry->mNodeOffset); *aSelLength = 0; // Now move the caret so that it is actually in the text node. // We do this to keep things in sync. // // In most cases, the user shouldn't see any movement in the caret // on screen. result = SetSelectionInternal(*aSelOffset, *aSelLength, PR_TRUE); return result; } } return NS_ERROR_FAILURE; } nsresult nsTextServicesDocument::GetUncollapsedSelection(nsITextServicesDocument::TSDBlockSelectionStatus *aSelStatus, PRInt32 *aSelOffset, PRInt32 *aSelLength) { nsresult result; nsCOMPtr selection; nsCOMPtr range; OffsetEntry *entry; result = mSelCon->GetSelection(nsISelectionController::SELECTION_NORMAL, getter_AddRefs(selection)); if (NS_FAILED(result)) return result; if (!selection) return NS_ERROR_FAILURE; // It is assumed that the calling function has made sure that the // selection is not collapsed, and that the input params to this // method are initialized to some defaults. nsCOMPtr startParent, endParent; PRInt32 startOffset, endOffset; PRInt32 rangeCount, tableCount, i; PRInt32 e1s1, e1s2, e2s1, e2s2; OffsetEntry *eStart, *eEnd; PRInt32 eStartOffset, eEndOffset; tableCount = mOffsetTable.Count(); // Get pointers to the first and last offset entries // in the table. eStart = (OffsetEntry *)mOffsetTable[0]; if (tableCount > 1) eEnd = (OffsetEntry *)mOffsetTable[tableCount - 1]; else eEnd = eStart; eStartOffset = eStart->mNodeOffset; eEndOffset = eEnd->mNodeOffset + eEnd->mLength; result = selection->GetRangeCount(&rangeCount); if (NS_FAILED(result)) return result; // Find the first range in the selection that intersects // the current text block. for (i = 0; i < rangeCount; i++) { result = selection->GetRangeAt(i, getter_AddRefs(range)); if (NS_FAILED(result)) return result; result = GetRangeEndPoints(range, getter_AddRefs(startParent), &startOffset, getter_AddRefs(endParent), &endOffset); if (NS_FAILED(result)) return result; result = ComparePoints(eStart->mNode, eStartOffset, endParent, endOffset, &e1s2); if (NS_FAILED(result)) return result; result = ComparePoints(eEnd->mNode, eEndOffset, startParent, startOffset, &e2s1); if (NS_FAILED(result)) return result; // Break out of the loop if the text block intersects the current range. if (e1s2 <= 0 && e2s1 >= 0) break; } // We're done if we didn't find an intersecting range. if (rangeCount < 1 || e1s2 > 0 || e2s1 < 0) { *aSelStatus = nsITextServicesDocument::eBlockOutside; *aSelOffset = *aSelLength = -1; return NS_OK; } // Now that we have an intersecting range, find out more info: result = ComparePoints(eStart->mNode, eStartOffset, startParent, startOffset, &e1s1); if (NS_FAILED(result)) return result; result = ComparePoints(eEnd->mNode, eEndOffset, endParent, endOffset, &e2s2); if (NS_FAILED(result)) return result; if (rangeCount > 1) { // There are multiple selection ranges, we only deal // with the first one that intersects the current, // text block, so mark this a as a partial. *aSelStatus = nsITextServicesDocument::eBlockPartial; } else if (e1s1 > 0 && e2s2 < 0) { // The range extends beyond the start and // end of the current text block. *aSelStatus = nsITextServicesDocument::eBlockInside; } else if (e1s1 <= 0 && e2s2 >= 0) { // The current text block contains the entire // range. *aSelStatus = nsITextServicesDocument::eBlockContains; } else { // The range partially intersects the block. *aSelStatus = nsITextServicesDocument::eBlockPartial; } // Now create a range based on the intersection of the // text block and range: nsCOMPtr p1, p2; PRInt32 o1, o2; // The start of the range will be the rightmost // start node. if (e1s1 >= 0) { p1 = do_QueryInterface(eStart->mNode); o1 = eStartOffset; } else { p1 = startParent; o1 = startOffset; } // The end of the range will be the leftmost // end node. if (e2s2 <= 0) { p2 = do_QueryInterface(eEnd->mNode); o2 = eEndOffset; } else { p2 = endParent; o2 = endOffset; } result = CreateRange(p1, o1, p2, o2, getter_AddRefs(range)); if (NS_FAILED(result)) return result; // Now iterate over this range to figure out the selection's // block offset and length. nsCOMPtr iter; result = CreateContentIterator(range, getter_AddRefs(iter)); if (NS_FAILED(result)) return result; // Find the first text node in the range. PRBool found; nsCOMPtr content; result = iter->First(); if (NS_FAILED(result)) return result; if (! IsTextNode(p1)) { found = PR_FALSE; while (iter->IsDone() == NS_ENUMERATOR_FALSE) { result = iter->CurrentNode(getter_AddRefs(content)); if (NS_FAILED(result)) return result; if (!content) return NS_ERROR_FAILURE; if (IsTextNode(content)) { p1 = do_QueryInterface(content); if (!p1) return NS_ERROR_FAILURE; o1 = 0; found = PR_TRUE; break; } result = iter->Next(); if (NS_FAILED(result)) return result; } if (!found) return NS_ERROR_FAILURE; } // Find the last text node in the range. result = iter->Last(); if (NS_FAILED(result)) return result; if (! IsTextNode(p2)) { found = PR_FALSE; while (iter->IsDone() == NS_ENUMERATOR_FALSE) { result = iter->CurrentNode(getter_AddRefs(content)); if (NS_FAILED(result)) return result; if (!content) return NS_ERROR_FAILURE; if (IsTextNode(content)) { p2 = do_QueryInterface(content); if (!p2) return NS_ERROR_FAILURE; nsString str; result = p2->GetNodeValue(str); if (NS_FAILED(result)) return result; o2 = str.Length(); found = PR_TRUE; break; } result = iter->Prev(); if (NS_FAILED(result)) return result; } if (!found) return NS_ERROR_FAILURE; } found = PR_FALSE; *aSelLength = 0; for (i = 0; i < tableCount; i++) { entry = (OffsetEntry *)mOffsetTable[i]; if (!entry) return NS_ERROR_FAILURE; if (!found) { if (entry->mNode == p1.get() && entry->mNodeOffset <= o1 && o1 <= (entry->mNodeOffset + entry->mLength)) { *aSelOffset = entry->mStrOffset + (o1 - entry->mNodeOffset); if (p1 == p2 && entry->mNodeOffset <= o2 && o2 <= (entry->mNodeOffset + entry->mLength)) { // The start and end of the range are in the same offset // entry. Calculate the length of the range then we're done. *aSelLength = o2 - o1; break; } else { // Add the length of the sub string in this offset entry // that follows the start of the range. *aSelLength = entry->mLength - (o1 - entry->mNodeOffset); } found = PR_TRUE; } } else // found { if (entry->mNode == p2.get() && entry->mNodeOffset <= o2 && o2 <= (entry->mNodeOffset + entry->mLength)) { // We found the end of the range. Calculate the length of the // sub string that is before the end of the range, then we're done. *aSelLength += o2 - entry->mNodeOffset; break; } else { // The entire entry must be in the range. *aSelLength += entry->mLength; } } } return result; } PRBool nsTextServicesDocument::SelectionIsCollapsed() { return(mSelStartIndex == mSelEndIndex && mSelStartOffset == mSelEndOffset); } PRBool nsTextServicesDocument::SelectionIsValid() { return(mSelStartIndex >= 0); } nsresult nsTextServicesDocument::ComparePoints(nsIDOMNode* aParent1, PRInt32 aOffset1, nsIDOMNode* aParent2, PRInt32 aOffset2, PRInt32 *aResult) { nsresult result; // Compare two node/offset pairs. // // Return -1 if node1 < node2 // Return 0 if node1 == node2 // Return 1 if node1 > node2 *aResult = 0; if (aParent1 == aParent2 && aOffset1 == aOffset2) return NS_OK; nsCOMPtr range; result = nsComponentManager::CreateInstance(kCDOMRangeCID, nsnull, NS_GET_IID(nsIDOMRange), getter_AddRefs(range)); if (NS_FAILED(result)) return result; if (!range) return NS_ERROR_FAILURE; result = range->SetStart(aParent1, aOffset1); if (NS_FAILED(result)) return result; result = range->SetEnd(aParent2, aOffset2); if (NS_SUCCEEDED(result)) *aResult = -1; else *aResult = 1; return NS_OK; } nsresult nsTextServicesDocument::GetRangeEndPoints(nsIDOMRange *aRange, nsIDOMNode **aStartParent, PRInt32 *aStartOffset, nsIDOMNode **aEndParent, PRInt32 *aEndOffset) { nsresult result; if (!aRange || !aStartParent || !aStartOffset || !aEndParent || !aEndOffset) return NS_ERROR_NULL_POINTER; result = aRange->GetStartContainer(aStartParent); if (NS_FAILED(result)) return result; if (!aStartParent) return NS_ERROR_FAILURE; result = aRange->GetStartOffset(aStartOffset); if (NS_FAILED(result)) return result; result = aRange->GetEndContainer(aEndParent); if (NS_FAILED(result)) return result; if (!aEndParent) return NS_ERROR_FAILURE; result = aRange->GetEndOffset(aEndOffset); return result; } nsresult nsTextServicesDocument::CreateRange(nsIDOMNode *aStartParent, PRInt32 aStartOffset, nsIDOMNode *aEndParent, PRInt32 aEndOffset, nsIDOMRange **aRange) { nsresult result; result = nsComponentManager::CreateInstance(kCDOMRangeCID, nsnull, NS_GET_IID(nsIDOMRange), (void **)aRange); if (NS_FAILED(result)) return result; if (!*aRange) return NS_ERROR_NULL_POINTER; result = (*aRange)->SetStart(aStartParent, aStartOffset); if (NS_SUCCEEDED(result)) result = (*aRange)->SetEnd(aEndParent, aEndOffset); if (NS_FAILED(result)) { NS_RELEASE((*aRange)); *aRange = 0; } return result; } nsresult nsTextServicesDocument::FirstTextNodeInCurrentBlock(nsIContentIterator *iter) { nsresult result; if (!iter) return NS_ERROR_NULL_POINTER; nsCOMPtr content; nsCOMPtr last; // Walk backwards over adjacent text nodes until // we hit a block boundary: while (NS_ENUMERATOR_FALSE == iter->IsDone()) { result = iter->CurrentNode(getter_AddRefs(content)); if (NS_FAILED(result)) return result; if (IsTextNode(content)) { if (!last || HasSameBlockNodeParent(content, last)) last = content; else { // We're done, the current text node is in a // different block. break; } } else if (last && IsBlockNode(content)) break; result = iter->Prev(); if (NS_FAILED(result)) return result; } if (last) result = iter->PositionAt(last); // XXX: What should we return if last is null? return NS_OK; } nsresult nsTextServicesDocument::FirstTextNodeInPrevBlock(nsIContentIterator *aIterator) { nsCOMPtr content; nsresult result; if (!aIterator) return NS_ERROR_NULL_POINTER; // XXX: What if mIterator is not currently on a text node? // Make sure mIterator is pointing to the first text node in the // current block: result = FirstTextNodeInCurrentBlock(aIterator); if (NS_FAILED(result)) return NS_ERROR_FAILURE; // Point mIterator to the first node before the first text node: result = aIterator->Prev(); if (NS_FAILED(result)) return result; // Now find the first text node of the next block: return FirstTextNodeInCurrentBlock(aIterator); } nsresult nsTextServicesDocument::FirstTextNodeInNextBlock(nsIContentIterator *aIterator) { nsCOMPtr content; nsCOMPtr prev; PRBool crossedBlockBoundary = PR_FALSE; nsresult result; if (!aIterator) return NS_ERROR_NULL_POINTER; while (NS_ENUMERATOR_FALSE == aIterator->IsDone()) { result = aIterator->CurrentNode(getter_AddRefs(content)); if (NS_FAILED(result)) return result; if (!content) return NS_ERROR_FAILURE; if (IsTextNode(content)) { if (!crossedBlockBoundary && (!prev || HasSameBlockNodeParent(prev, content))) prev = content; else break; } else if (IsBlockNode(content)) crossedBlockBoundary = PR_TRUE; result = aIterator->Next(); if (NS_FAILED(result)) return result; } return NS_OK; } nsresult nsTextServicesDocument::GetFirstTextNodeInPrevBlock(nsIContent **aContent) { nsCOMPtr content; nsresult result; if (!aContent) return NS_ERROR_NULL_POINTER; *aContent = 0; // Save the iterator's current content node so we can restore // it when we are done: mIterator->CurrentNode(getter_AddRefs(content)); result = FirstTextNodeInPrevBlock(mIterator); if (NS_FAILED(result)) { // Try to restore the iterator before returning. mIterator->PositionAt(content); return result; } if (mIterator->IsDone() == NS_ENUMERATOR_FALSE) { result = mIterator->CurrentNode(aContent); if (NS_FAILED(result)) { // Try to restore the iterator before returning. mIterator->PositionAt(content); return result; } } // Restore the iterator: result = mIterator->PositionAt(content); return result; } nsresult nsTextServicesDocument::GetFirstTextNodeInNextBlock(nsIContent **aContent) { nsCOMPtr content; nsresult result; if (!aContent) return NS_ERROR_NULL_POINTER; *aContent = 0; // Save the iterator's current content node so we can restore // it when we are done: mIterator->CurrentNode(getter_AddRefs(content)); result = FirstTextNodeInNextBlock(mIterator); if (NS_FAILED(result)) { // Try to restore the iterator before returning. mIterator->PositionAt(content); return result; } if (mIterator->IsDone() == NS_ENUMERATOR_FALSE) { result = mIterator->CurrentNode(aContent); if (NS_FAILED(result)) { // Try to restore the iterator before returning. mIterator->PositionAt(content); return result; } } // Restore the iterator: result = mIterator->PositionAt(content); return result; } nsresult nsTextServicesDocument::CreateOffsetTable(nsString *aStr) { nsresult result = NS_OK; nsCOMPtr content; nsCOMPtr first; nsCOMPtr prev; if (!mIterator) return NS_ERROR_NULL_POINTER; ClearOffsetTable(); if (aStr) aStr->SetLength(0); if (mIteratorStatus == nsTextServicesDocument::eIsDone) return NS_OK; // The text service could have added text nodes to the beginning // of the current block and called this method again. Make sure // we really are at the beginning of the current block: result = FirstTextNodeInCurrentBlock(mIterator); if (NS_FAILED(result)) return result; PRInt32 offset = 0; while (NS_ENUMERATOR_FALSE == mIterator->IsDone()) { result = mIterator->CurrentNode(getter_AddRefs(content)); if (NS_FAILED(result)) return result; if (!content) return NS_ERROR_FAILURE; if (IsTextNode(content)) { if (!prev || HasSameBlockNodeParent(prev, content)) { nsCOMPtr node = do_QueryInterface(content); if (node) { nsString str; result = node->GetNodeValue(str); if (NS_FAILED(result)) return result; // Add an entry for this text node into the offset table: OffsetEntry *entry = new OffsetEntry(node, offset, str.Length()); if (!entry) return NS_ERROR_OUT_OF_MEMORY; mOffsetTable.AppendElement((void *)entry); offset += str.Length(); if (aStr) { // Append the text node's string to the output string: if (!first) *aStr = str; else *aStr += str; } } prev = content; if (!first) first = content; } else break; } else if (IsBlockNode(content)) break; result = mIterator->Next(); if (NS_FAILED(result)) return result; } if (first) { // Always leave the iterator pointing at the first // text node of the current block! mIterator->PositionAt(first); } else { // If we never ran across a text node, the iterator // might have been pointing to something invalid to // begin with. mIteratorStatus = nsTextServicesDocument::eIsDone; } return result; } nsresult nsTextServicesDocument::RemoveInvalidOffsetEntries() { OffsetEntry *entry; PRInt32 i = 0; while (i < mOffsetTable.Count()) { entry = (OffsetEntry *)mOffsetTable[i]; if (!entry->mIsValid) { if (!mOffsetTable.RemoveElementAt(i)) return NS_ERROR_FAILURE; if (mSelStartIndex >= 0 && mSelStartIndex >= i) { // We are deleting an entry that comes before // mSelStartIndex, decrement mSelStartIndex so // that it points to the correct entry! NS_ASSERTION(i != mSelStartIndex, "Invalid selection index."); --mSelStartIndex; --mSelEndIndex; } } else i++; } return NS_OK; } nsresult nsTextServicesDocument::ClearOffsetTable() { PRInt32 i; for (i = 0; i < mOffsetTable.Count(); i++) { OffsetEntry *entry = (OffsetEntry *)mOffsetTable[i]; if (entry) delete entry; } mOffsetTable.Clear(); return NS_OK; } nsresult nsTextServicesDocument::SplitOffsetEntry(PRInt32 aTableIndex, PRInt32 aNewEntryLength) { OffsetEntry *entry = (OffsetEntry *)mOffsetTable[aTableIndex]; NS_ASSERTION((aNewEntryLength > 0), "aNewEntryLength <= 0"); NS_ASSERTION((aNewEntryLength < entry->mLength), "aNewEntryLength >= mLength"); if (aNewEntryLength < 1 || aNewEntryLength >= entry->mLength) return NS_ERROR_FAILURE; PRInt32 oldLength = entry->mLength - aNewEntryLength; OffsetEntry *newEntry = new OffsetEntry(entry->mNode, entry->mStrOffset + oldLength, aNewEntryLength); if (!newEntry) return NS_ERROR_OUT_OF_MEMORY; if (!mOffsetTable.InsertElementAt(newEntry, aTableIndex + 1)) { delete newEntry; return NS_ERROR_FAILURE; } // Adjust entry fields: entry->mLength = oldLength; newEntry->mNodeOffset = entry->mNodeOffset + oldLength; return NS_OK; } nsresult nsTextServicesDocument::NodeHasOffsetEntry(nsIDOMNode *aNode, PRBool *aHasEntry, PRInt32 *aEntryIndex) { OffsetEntry *entry; PRInt32 i; if (!aNode || !aHasEntry || !aEntryIndex) return NS_ERROR_NULL_POINTER; for (i = 0; i < mOffsetTable.Count(); i++) { entry = (OffsetEntry *)mOffsetTable[i]; if (!entry) return NS_ERROR_FAILURE; if (entry->mNode == aNode) { *aHasEntry = PR_TRUE; *aEntryIndex = i; return NS_OK; } } *aHasEntry = PR_FALSE; *aEntryIndex = -1; return NS_OK; } void nsTextServicesDocument::PrintOffsetTable() { OffsetEntry *entry; PRInt32 i; for (i = 0; i < mOffsetTable.Count(); i++) { entry = (OffsetEntry *)mOffsetTable[i]; printf("ENTRY %4d: 0x%.8x %c %c %4d %4d %4d\n", i, (PRInt32)entry->mNode, entry->mIsValid ? 'V' : 'N', entry->mIsInsertedText ? 'I' : 'B', entry->mNodeOffset, entry->mStrOffset, entry->mLength); } fflush(stdout); } void nsTextServicesDocument::PrintContentNode(nsIContent *aContent) { nsString tmpStr, str; char tmpBuf[256]; nsIAtom *atom = 0; nsresult result; aContent->GetTag(atom); atom->ToString(tmpStr); tmpStr.ToCString(tmpBuf, 256); printf("%s", tmpBuf); nsCOMPtr node = do_QueryInterface(aContent); if (node) { PRUint16 type; result = node->GetNodeType(&type); if (NS_FAILED(result)) return; if (nsIDOMNode::TEXT_NODE == type) { result = node->GetNodeValue(str); if (NS_FAILED(result)) return; str.ToCString(tmpBuf, 256); printf(": \"%s\"", tmpBuf); } } printf("\n"); fflush(stdout); }